use crate::backend::Headless;
use crate::color::Style;
use crate::color::{Color, Tint};
use crate::grid::{Grid, Offset, Pos, Rect};
use crate::terminal::Terminal;
use crate::text::Line;
use crate::tile::Tile;
use super::{Layer, Surface};
fn screen(grid: &mut Grid) -> Surface<'_> {
let area = grid.size().to_rect();
Surface::new(grid, area, 0)
}
fn render_local_top_left(surface: &mut Surface<'_>, ch: char) {
let local = surface.area().at_origin();
surface.put((local.left(), local.top()), ch, Style::default());
}
#[test]
fn local_area_is_always_zero_origin_regardless_of_where_area_sits() {
let mut grid = Grid::new(10, 10);
let mut surface = screen(&mut grid);
let scoped = surface.scope(Rect::new(3, 4, 5, 6));
assert_eq!(scoped.area(), Rect::new(3, 4, 5, 6));
assert_eq!(scoped.area().at_origin(), Rect::new(0, 0, 5, 6));
}
#[test]
fn a_widget_placed_via_local_area_lands_the_same_way_at_the_origin_and_at_an_offset() {
let mut grid_origin = Grid::new(4, 4);
render_local_top_left(&mut screen(&mut grid_origin), 'X');
let mut grid_offset = Grid::new(10, 10);
{
let mut surface = screen(&mut grid_offset);
let mut scoped = surface.scope(Rect::new(3, 3, 4, 4));
render_local_top_left(&mut scoped, 'X');
}
assert_eq!(grid_origin[Pos::new(0, 0)].glyph(), 'X');
assert_eq!(grid_offset[Pos::new(3, 3)].glyph(), 'X');
}
#[test]
fn blit_reads_the_sources_layer_0_even_when_this_surface_is_on_a_different_layer() {
let mut src = Grid::new(2, 2);
src.put_tile(0, (0, 0), Tile::new('x', Style::default()));
let mut dst = Grid::new(4, 4);
let mut surface = Surface::new(&mut dst, Rect::new(0, 0, 4, 4), 0);
surface.on_layer(3).blit(&src, 1, 1);
assert_eq!(dst.tile(3, (1, 1)).map(Tile::glyph), Some('x'));
assert_eq!(dst.tile(0, (1, 1)).map(Tile::glyph), Some(' '));
}
#[test]
fn blit_stamps_a_grid_at_a_local_offset() {
let mut src = Grid::new(2, 2);
src.put_tile(0, (0, 0), Tile::new('x', Style::default()));
src.put_tile(0, (1, 1), Tile::new('y', Style::default()));
let mut dst = Grid::new(5, 5);
screen(&mut dst).blit(&src, 2, 1);
assert_eq!(dst[Pos::new(2, 1)].glyph(), 'x');
assert_eq!(dst[Pos::new(3, 2)].glyph(), 'y');
assert_eq!(dst[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn blit_clips_to_this_surfaces_clip_instead_of_skipping_the_whole_call() {
let mut src = Grid::new(3, 3);
for y in 0..3 {
for x in 0..3 {
src.put_tile(0, (x, y), Tile::new('#', Style::default()));
}
}
let mut dst = Grid::new(5, 5);
{
let mut surface = screen(&mut dst);
surface.clip(Rect::new(1, 1, 2, 2)).blit(&src, 0, 0);
}
assert_eq!(dst[Pos::new(1, 1)].glyph(), '#');
assert_eq!(dst[Pos::new(2, 2)].glyph(), '#');
assert_eq!(dst[Pos::new(2, 0)].glyph(), ' ');
assert_eq!(dst[Pos::new(0, 2)].glyph(), ' ');
}
#[test]
fn blit_is_a_no_op_for_a_zero_sized_grid() {
let src = Grid::new(1, 0);
let mut dst = Grid::new(4, 4);
screen(&mut dst).blit(&src, 0, 0);
assert_eq!(dst[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn blit_is_a_no_op_when_the_whole_footprint_is_cropped_before_the_translated_origin() {
let mut src = Grid::new(2, 2);
src.put_tile(0, (0, 0), Tile::new('x', Style::default()));
let mut dst = Grid::new(4, 4);
{
let mut surface = screen(&mut dst);
let mut view = surface.translate((100, 0));
view.blit(&src, 0, 0);
}
assert_eq!(dst[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn blit_is_a_no_op_when_the_shifted_x_origin_overflows_u16() {
let mut src = Grid::new(2, 2);
src.put_tile(0, (0, 0), Tile::new('x', Style::default()));
let mut dst = Grid::new(4, 4);
{
let mut surface = screen(&mut dst);
let mut view = surface.translate((-100_000, 0));
view.blit(&src, 0, 0);
}
assert_eq!(dst[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn blit_is_a_no_op_when_the_shifted_y_origin_overflows_u16() {
let mut src = Grid::new(2, 2);
src.put_tile(0, (0, 0), Tile::new('x', Style::default()));
let mut dst = Grid::new(4, 4);
{
let mut surface = screen(&mut dst);
let mut view = surface.translate((0, -100_000));
view.blit(&src, 0, 0);
}
assert_eq!(dst[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn blit_is_a_no_op_when_the_destination_footprint_misses_the_clip_entirely() {
let mut src = Grid::new(2, 2);
src.put_tile(0, (0, 0), Tile::new('x', Style::default()));
let mut dst = Grid::new(5, 5);
{
let mut surface = screen(&mut dst);
let mut clipped = surface.clip(Rect::new(0, 0, 1, 1));
clipped.blit(&src, 3, 3);
}
assert_eq!(dst[Pos::new(3, 3)].glyph(), ' ');
}
#[test]
fn put_span_takes_any_as_ref_str_row() {
use alloc::string::String;
use alloc::vec::Vec;
let mut grid = Grid::new(4, 4);
let rows: Vec<String> = (0..2)
.map(|row| {
(0..2)
.map(|col| if (row, col) == (0, 0) { 'C' } else { ' ' })
.collect()
})
.collect();
assert_eq!(
screen(&mut grid).put_span((0, 0), &rows, Style::default()),
Some(())
);
assert_eq!(grid[Pos::new(0, 0)].span(), (2, 2));
}
#[test]
fn put_span_reports_why_a_span_did_not_draw() {
let mut grid = Grid::new(4, 4);
let area = Rect::new(0, 0, 2, 2);
let mut surface = Surface::new(&mut grid, area, 0);
let style = Style::default();
assert_eq!(surface.put_span((0, 0), &[] as &[&str], style), None);
assert_eq!(surface.put_span((0, 0), &[""], style), None);
assert_eq!(surface.put_span((0, 0), &["ab", "c"], style), None);
assert_eq!(surface.put_span((1, 1), &["ab"], style), None);
assert_eq!(surface.put_span((0, 0), &["ab"], style), Some(()));
}
#[test]
fn put_span_refuses_an_axis_wider_than_255_cells() {
let mut grid = Grid::new(300, 2);
let mut surface = screen(&mut grid);
let row = "a".repeat(256);
assert_eq!(
surface.put_span((0, 0), &[row.as_str()], Style::default()),
None
);
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn put_span_refuses_and_writes_nothing_when_a_later_row_is_longer_than_the_first() {
let mut grid = Grid::new(4, 4);
let mut surface = screen(&mut grid);
assert_eq!(
surface.put_span((0, 0), &["ab", "abc"], Style::default()),
None
);
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
assert_eq!(grid[Pos::new(0, 1)].glyph(), ' ');
}
#[test]
fn put_span_uniform_writes_the_anchor_once_and_fills_the_rest() {
let mut grid = Grid::new(4, 4);
assert_eq!(
screen(&mut grid).put_span_uniform((1, 1), (2, 2), 'C', '.', Style::default()),
Some(())
);
assert_eq!(grid[Pos::new(1, 1)].glyph(), 'C');
assert_eq!(grid[Pos::new(1, 1)].span(), (2, 2));
assert_eq!(grid[Pos::new(2, 2)].glyph(), '.');
assert_eq!(grid.span_owner(0, 2, 2), Some(Pos::new(1, 1)));
}
#[test]
fn put_span_uniform_writes_to_this_surfaces_layer() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface
.on_layer(2)
.put_span_uniform((0, 0), (2, 1), 'C', ' ', Style::default())
.expect("span write");
}
assert_eq!(grid.span_owner(2, 1, 0), Some(Pos::new(0, 0)));
assert_eq!(grid.span_owner(0, 1, 0), None);
}
#[test]
fn put_span_uniform_refuses_a_footprint_that_leaves_the_surfaces_area() {
let mut grid = Grid::new(4, 4);
let area = Rect::new(0, 0, 2, 2);
let mut surface = Surface::new(&mut grid, area, 0);
let style = Style::default();
assert_eq!(
surface.put_span_uniform((1, 0), (2, 1), 'C', ' ', style),
None
);
assert_eq!(
surface.put_span_uniform((0, 1), (1, 2), 'C', ' ', style),
None
);
assert_eq!(
surface.put_span_uniform((0, 0), (0, 1), 'C', ' ', style),
None
);
assert_eq!(
surface.put_span_uniform((0, 0), (2, 2), 'C', ' ', style),
Some(())
);
}
#[test]
fn styled_surface_forwards_both_span_calls() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
let mut styled = surface.with_style(Style::new().fg(Color::RED));
styled.put_span((0, 0), &["ab"]).expect("span write");
styled
.put_span_uniform((0, 1), (2, 1), 'C', ' ')
.expect("span write");
}
assert_eq!(grid[Pos::new(0, 0)].style().foreground(), Color::RED);
assert_eq!(grid[Pos::new(0, 1)].style().foreground(), Color::RED);
assert_eq!(grid[Pos::new(0, 1)].span(), (2, 1));
}
#[test]
fn styled_surface_forwards_put_print_fill_rect_and_put_offset() {
let style = Style::new().fg(Color::RED);
let mut grid = Grid::new(6, 4);
{
let mut surface = screen(&mut grid);
let mut styled = surface.with_style(style);
assert_eq!(styled.style(), style);
styled.put((0, 0), 'a');
styled.print((1, 0), "bc");
styled.fill_rect(Rect::new(0, 1, 2, 1), '#');
styled.put_offset((0, 2), Offset::new(3, -3), 'x');
styled.surface().print_line((0, 3), &Line::from("d"));
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(0, 0)].style().foreground(), Color::RED);
assert_eq!(grid[Pos::new(1, 0)].glyph(), 'b');
assert_eq!(grid[Pos::new(2, 0)].glyph(), 'c');
assert_eq!(grid[Pos::new(0, 1)].glyph(), '#');
assert_eq!(grid[Pos::new(1, 1)].glyph(), '#');
let tile = grid.tile(0, Pos::new(0, 2)).unwrap();
assert_eq!((tile.dx(), tile.dy()), (3, -3));
assert_eq!(grid[Pos::new(0, 3)].glyph(), 'd');
}
#[test]
fn with_tint_applies_to_the_cell_it_writes() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface
.with_tint(Tint::multiply(128, 64, 32))
.put((1, 1), '@', Style::default());
}
assert_eq!(grid[Pos::new(1, 1)].glyph(), '@');
assert_eq!(grid.tint(0, 1, 1), Tint::multiply(128, 64, 32));
}
#[test]
fn an_untinted_surface_leaves_the_side_table_alone() {
let mut grid = Grid::new(4, 4);
screen(&mut grid).put((1, 1), '@', Style::default());
assert_eq!(grid.tint(0, 1, 1), Tint::None);
}
#[test]
fn with_tint_applies_to_every_cell_fill_rect_touches() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface.with_tint(Tint::multiply(128, 64, 32)).fill_rect(
Rect::new(1, 1, 2, 2),
'#',
Style::default(),
);
}
for (x, y) in [(1, 1), (2, 1), (1, 2), (2, 2)] {
assert_eq!(grid[Pos::new(x, y)].glyph(), '#');
assert_eq!(grid.tint(0, x, y), Tint::multiply(128, 64, 32));
}
}
#[test]
fn fill_rect_with_a_wide_glyph_falls_back_to_the_put_loop() {
let rect = Rect::new(0, 0, 6, 1);
let mut via_fill_rect = Grid::new(8, 1);
screen(&mut via_fill_rect).fill_rect(rect, '\u{6f22}', Style::default());
let mut via_put = Grid::new(8, 1);
{
let mut surface = screen(&mut via_put);
for pos in rect {
surface.put(pos, '\u{6f22}', Style::default());
}
}
for x in 0..8 {
assert_eq!(
via_fill_rect[Pos::new(x, 0)],
via_put[Pos::new(x, 0)],
"cell ({x}, 0)"
);
}
}
#[test]
fn translated_fill_rect_and_clear_region_clip_a_rect_much_larger_than_the_area() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
let mut view = surface.translate((1, 1));
view.fill_rect(Rect::new(0, 0, 60_000, 60_000), '#', Style::default());
}
for y in 0..4 {
for x in 0..4 {
assert_eq!(grid[Pos::new(x, y)].glyph(), '#', "cell ({x}, {y})");
}
}
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface.fill_rect(Rect::new(0, 0, 4, 4), '#', Style::default());
let mut view = surface.translate((1, 1));
view.clear_region(Rect::new(0, 0, 60_000, 60_000));
}
for y in 0..4 {
for x in 0..4 {
assert_eq!(grid[Pos::new(x, y)].glyph(), ' ', "cell ({x}, {y})");
}
}
}
#[test]
fn tinted_fill_rect_clips_a_rect_much_larger_than_the_area() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface.with_tint(Tint::multiply(128, 64, 32)).fill_rect(
Rect::new(0, 0, 60_000, 60_000),
'#',
Style::default(),
);
}
for y in 0..4 {
for x in 0..4 {
assert_eq!(grid[Pos::new(x, y)].glyph(), '#', "cell ({x}, {y})");
assert_eq!(
grid.tint(0, x, y),
Tint::multiply(128, 64, 32),
"cell ({x}, {y})"
);
}
}
}
#[test]
fn wide_glyph_fill_rect_clips_a_rect_much_larger_than_the_area() {
let mut via_fill_rect = Grid::new(4, 4);
screen(&mut via_fill_rect).fill_rect(
Rect::new(0, 0, 60_000, 60_000),
'\u{6f22}',
Style::default(),
);
let mut via_put = Grid::new(4, 4);
{
let mut surface = screen(&mut via_put);
for y in 0..4 {
for x in 0..4 {
surface.put((x, y), '\u{6f22}', Style::default());
}
}
}
for y in 0..4 {
for x in 0..4 {
assert_eq!(
via_fill_rect[Pos::new(x, y)],
via_put[Pos::new(x, y)],
"cell ({x}, {y})"
);
}
}
}
#[test]
fn fill_rect_clears_a_span_it_partially_overwrites() {
use crate::tile::TileFlags;
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface
.put_span((0, 0), &["C=", "[]"], Style::default())
.expect("2x2 span fits in a 4x4 grid");
surface.fill_rect(Rect::new(1, 0, 3, 3), '#', Style::default());
}
assert!(
!grid
.tile(0, (0, 0))
.unwrap()
.flags()
.contains(TileFlags::SPAN_ANCHOR)
);
}
#[test]
fn clear_region_clears_a_span_it_partially_overwrites() {
use crate::tile::TileFlags;
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface
.put_span((0, 0), &["C=", "[]"], Style::default())
.expect("2x2 span fits in a 4x4 grid");
surface.clear_region(Rect::new(1, 0, 3, 3));
}
assert!(
!grid
.tile(0, (0, 0))
.unwrap()
.flags()
.contains(TileFlags::SPAN_ANCHOR)
);
}
#[test]
fn fill_rect_with_a_zero_sized_rect_is_a_no_op() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface.fill_rect(Rect::new(1, 1, 0, 3), '#', Style::default());
surface.fill_rect(Rect::new(1, 1, 3, 0), '#', Style::default());
}
for y in 0..4 {
for x in 0..4 {
assert_eq!(grid[Pos::new(x, y)].glyph(), ' ', "cell ({x}, {y})");
}
}
}
#[test]
fn clear_region_with_a_zero_sized_rect_is_a_no_op() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface.fill_rect(Rect::new(0, 0, 4, 4), '#', Style::default());
surface.clear_region(Rect::new(1, 1, 0, 2));
surface.clear_region(Rect::new(1, 1, 2, 0));
}
for y in 0..4 {
for x in 0..4 {
assert_eq!(grid[Pos::new(x, y)].glyph(), '#', "cell ({x}, {y})");
}
}
}
#[test]
fn with_tint_lands_on_the_span_anchor_only() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface
.with_tint(Tint::multiply(200, 200, 200))
.put_span((0, 0), &["ab", "cd"], Style::default())
.expect("span write");
}
assert_eq!(grid.tint(0, 0, 0), Tint::multiply(200, 200, 200));
assert_eq!(grid.tint(0, 1, 0), Tint::None);
assert_eq!(grid.tint(0, 1, 1), Tint::None);
}
#[test]
fn with_tint_applies_to_a_uniform_span_anchor() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface
.with_tint(Tint::mix(255, 0, 0, 128))
.put_span_uniform((1, 1), (2, 2), 'C', '.', Style::default())
.expect("span write");
}
assert_eq!(grid.tint(0, 1, 1), Tint::mix(255, 0, 0, 128));
assert_eq!(grid.tint(0, 2, 2), Tint::None);
}
#[test]
fn with_tint_is_not_applied_to_a_refused_span() {
let mut grid = Grid::new(4, 4);
let area = Rect::new(0, 0, 2, 2);
{
let mut surface = Surface::new(&mut grid, area, 0);
assert_eq!(
surface
.with_tint(Tint::multiply(1, 2, 3))
.put_span((1, 1), &["ab"], Style::default()),
None
);
}
assert_eq!(grid.tint(0, 1, 1), Tint::None);
}
#[test]
fn with_tint_survives_clip_and_on_layer() {
let mut grid = Grid::new(8, 4);
{
let mut surface = screen(&mut grid);
let mut tinted = surface.with_tint(Tint::multiply(9, 9, 9));
assert_eq!(tinted.tint(), Tint::multiply(9, 9, 9));
assert_eq!(
tinted.clip(Rect::new(0, 0, 4, 4)).tint(),
Tint::multiply(9, 9, 9)
);
assert_eq!(tinted.on_layer(2).tint(), Tint::multiply(9, 9, 9));
tinted.on_layer(2).put((1, 1), '@', Style::default());
}
assert_eq!(grid.tint(2, 1, 1), Tint::multiply(9, 9, 9));
}
#[test]
fn with_tint_on_a_write_outside_the_clip_does_not_allocate_the_layer() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface
.on_layer(7)
.with_tint(Tint::multiply(9, 9, 9))
.clip(Rect::new(0, 0, 2, 2))
.put((10, 10), '@', Style::default());
}
assert_eq!(grid.max_layer(), 0);
assert!(grid.tile(7, (0, 0)).is_none());
}
#[test]
fn with_tint_replaces_rather_than_composes() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
let mut outer = surface.with_tint(Tint::multiply(128, 128, 128));
outer
.with_tint(Tint::mix(255, 0, 0, 64))
.put((0, 0), '@', Style::default());
}
assert_eq!(grid.tint(0, 0, 0), Tint::mix(255, 0, 0, 64));
}
#[test]
fn clip_narrows_the_visible_rect_but_not_the_area() {
let mut grid = Grid::new(8, 4);
let area = Rect::new(0, 0, 8, 4);
let mut surface = screen(&mut grid);
let sub = surface.clip(Rect::new(2, 1, 4, 2));
assert_eq!(sub.clip_rect(), Rect::new(2, 1, 4, 2));
assert_eq!(sub.area(), area);
assert_eq!(sub.width(), 8);
assert_eq!(sub.height(), 4);
}
#[test]
fn clip_keeps_the_layer() {
let mut grid = Grid::new(4, 4);
let mut surface = screen(&mut grid);
let mut layer1 = surface.on_layer(1);
assert_eq!(layer1.clip(Rect::new(0, 0, 2, 2)).layer(), 1);
}
#[test]
fn layer_variants_order_low_to_high() {
assert!(Layer::World < Layer::Hud);
assert!(Layer::Hud < Layer::Overlay);
assert!(Layer::Overlay < Layer::Debug);
}
#[test]
fn layer_as_u8_matches_the_documented_grid_layer_ids() {
assert_eq!(Layer::World.as_u8(), 0);
assert_eq!(Layer::Hud.as_u8(), 1);
assert_eq!(Layer::Overlay.as_u8(), 2);
assert_eq!(Layer::Debug.as_u8(), 3);
}
#[test]
fn layer_default_is_world() {
assert_eq!(Layer::default(), Layer::World);
}
#[test]
fn layer_into_u8_matches_as_u8() {
assert_eq!(u8::from(Layer::Overlay), Layer::Overlay.as_u8());
}
#[test]
fn on_tier_matches_on_layer_with_the_tiers_u8() {
let mut grid = Grid::new(4, 4);
let mut surface = screen(&mut grid);
assert_eq!(surface.on_tier(Layer::Overlay).layer(), 2);
assert_eq!(
surface.on_layer(2).layer(),
surface.on_tier(Layer::Overlay).layer()
);
}
#[test]
fn on_tier_writes_land_on_the_tiers_grid_layer() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface
.on_tier(Layer::Overlay)
.put((0, 0), '@', Style::default());
}
assert_eq!(grid.tile(2, Pos::new(0, 0)).map(Tile::glyph), Some('@'));
assert_eq!(grid.tile(0, Pos::new(0, 0)).map(Tile::glyph), Some(' '));
assert_eq!(grid.tile(1, Pos::new(0, 0)), None);
}
#[test]
fn a_wide_char_at_the_clip_edge_refuses_to_write_its_spacer_outside_the_clip() {
let mut grid = Grid::new(8, 1);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 8, 1), 0);
surface
.clip(Rect::new(0, 0, 4, 1))
.put((3, 0), '\u{6f22}', Style::default());
assert_eq!(grid.tile(0, Pos::new(3, 0)).map(Tile::glyph), Some(' '));
assert!(
!grid
.tile(0, Pos::new(4, 0))
.is_some_and(|t| t.flags().contains(crate::tile::TileFlags::WIDE_CHAR_SPACER)),
"spacer must not be written outside the clip"
);
}
#[test]
#[cfg(not(feature = "egc"))]
fn put_does_not_tint_a_foreign_tile_when_put_tile_refuses_the_write() {
let mut grid = Grid::new(4, 1);
{
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 8, 1), 0);
surface
.with_tint(Tint::multiply(128, 64, 32))
.put((3, 0), 'X', Style::default());
surface
.with_tint(Tint::multiply(1, 2, 3))
.put((3, 0), '\u{6f22}', Style::default());
}
assert_eq!(grid[Pos::new(3, 0)].glyph(), 'X');
assert_eq!(grid.tint(0, 3, 0), Tint::multiply(128, 64, 32));
}
#[test]
#[cfg(feature = "egc")]
fn put_does_not_tint_a_foreign_tile_when_write_grapheme_refuses_the_write() {
let mut grid = Grid::new(4, 1);
{
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 8, 1), 0);
surface
.with_tint(Tint::multiply(128, 64, 32))
.put((3, 0), 'X', Style::default());
surface
.with_tint(Tint::multiply(1, 2, 3))
.put((3, 0), '\u{6f22}', Style::default());
}
assert_eq!(grid[Pos::new(3, 0)].glyph(), 'X');
assert_eq!(grid.tint(0, 3, 0), Tint::multiply(128, 64, 32));
}
#[test]
fn clip_intersects_rather_than_replaces_so_it_cannot_widen() {
let mut grid = Grid::new(8, 4);
let area = Rect::new(2, 1, 4, 2);
let mut surface = Surface::new(&mut grid, area, 0);
assert_eq!(surface.clip(Rect::new(0, 0, 8, 4)).clip_rect(), area);
assert_eq!(
surface.clip(Rect::new(0, 0, 4, 4)).clip_rect(),
Rect::new(2, 1, 2, 2)
);
}
#[test]
fn clip_does_not_widen_the_visible_region_after_scope_narrows_it() {
let mut grid = Grid::new(8, 4);
let mut surface = screen(&mut grid);
let mut scoped = surface.scope(Rect::new(1, 1, 2, 2));
assert_eq!(
scoped.clip(Rect::new(0, 0, 8, 4)).clip_rect(),
Rect::new(1, 1, 2, 2)
);
}
#[test]
fn scope_sets_the_area_and_intersects_the_clip() {
let mut grid = Grid::new(8, 4);
let mut surface = screen(&mut grid);
let mut clipped = surface.clip(Rect::new(0, 0, 4, 4));
let scoped = clipped.scope(Rect::new(2, 0, 4, 4));
assert_eq!(scoped.area(), Rect::new(2, 0, 4, 4));
assert_eq!(scoped.clip_rect(), Rect::new(2, 0, 2, 4));
}
#[test]
fn scope_inside_a_clipped_surface_cannot_widen_the_visible_region() {
let mut grid = Grid::new(8, 4);
let mut surface = screen(&mut grid);
let mut clipped = surface.clip(Rect::new(2, 1, 2, 2));
let scoped = clipped.scope(Rect::new(0, 0, 8, 4));
assert_eq!(scoped.area(), Rect::new(0, 0, 8, 4));
assert_eq!(scoped.clip_rect(), Rect::new(2, 1, 2, 2));
}
#[test]
fn scope_writes_outside_the_inherited_clip_are_dropped() {
let mut grid = Grid::new(8, 4);
{
let mut surface = screen(&mut grid);
let mut clipped = surface.clip(Rect::new(0, 0, 4, 4));
let mut scoped = clipped.scope(Rect::new(0, 0, 8, 4));
scoped.put((5, 0), 'a', Style::default());
scoped.put((1, 0), 'b', Style::default());
}
assert_eq!(grid[Pos::new(5, 0)].glyph(), ' ');
assert_eq!(grid[Pos::new(1, 0)].glyph(), 'b');
}
#[test]
fn clip_writes_outside_the_sub_rect_are_dropped() {
let mut grid = Grid::new(4, 2);
{
let mut surface = screen(&mut grid);
let mut top = surface.clip(Rect::new(0, 0, 4, 1));
top.put((1, 0), 'a', Style::default());
top.put((1, 1), 'b', Style::default());
}
assert_eq!(grid[Pos::new(1, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(1, 1)].glyph(), ' ');
}
#[test]
fn clip_to_one_row_drops_print_overflow_instead_of_wrapping_it() {
let mut grid = Grid::new(4, 2);
{
let mut surface = screen(&mut grid);
surface
.clip(Rect::new(0, 0, 4, 1))
.print((0, 0), "abcdef", Style::default());
}
assert_eq!(grid[Pos::new(3, 0)].glyph(), 'd');
assert_eq!(grid[Pos::new(0, 1)].glyph(), ' ');
}
#[test]
fn print_wraps_at_the_surfaces_own_width_not_at_clip_right() {
let mut grid = Grid::new(8, 2);
let mut surface = Surface::new(&mut grid, Rect::new(4, 0, 4, 2), 0);
surface.print((0, 0), "abcdef", Style::default());
assert_eq!(grid[Pos::new(4, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(7, 0)].glyph(), 'd');
assert_eq!(grid[Pos::new(4, 1)].glyph(), 'e');
assert_eq!(grid[Pos::new(5, 1)].glyph(), 'f');
}
#[test]
fn print_advances_past_an_embedded_newline_to_the_next_row_at_the_original_column() {
let mut grid = Grid::new(4, 4);
let mut surface = Surface::new(&mut grid, Rect::new(1, 0, 3, 4), 0);
surface.print((0, 0), "ab\ncd", Style::default());
assert_eq!(grid[Pos::new(1, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(2, 0)].glyph(), 'b');
assert_eq!(grid[Pos::new(1, 1)].glyph(), 'c');
assert_eq!(grid[Pos::new(2, 1)].glyph(), 'd');
assert_eq!(grid[Pos::new(3, 1)].glyph(), ' ');
}
#[test]
fn print_skips_a_leading_zero_width_grapheme_without_writing_or_advancing_the_column() {
let mut grid = Grid::new(4, 1);
screen(&mut grid).print((0, 0), "\u{0301}ab", Style::default());
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(1, 0)].glyph(), 'b');
assert_eq!(grid[Pos::new(2, 0)].glyph(), ' ');
}
#[test]
fn print_line_measures_its_span_skip_threshold_against_the_surfaces_own_width() {
use crate::text::Span;
use alloc::vec;
let mut grid = Grid::new(8, 1);
let mut surface = Surface::new(&mut grid, Rect::new(4, 0, 4, 1), 0);
let line = Line::from(vec![Span::raw("ab"), Span::raw("cd")]);
surface.print_line((0, 0), &line);
assert_eq!(grid[Pos::new(4, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(5, 0)].glyph(), 'b');
assert_eq!(grid[Pos::new(6, 0)].glyph(), 'c');
assert_eq!(grid[Pos::new(7, 0)].glyph(), 'd');
}
#[test]
fn clip_makes_put_span_measure_its_footprint_against_the_sub_rect() {
let mut grid = Grid::new(4, 3);
{
let mut surface = screen(&mut grid);
surface
.clip(Rect::new(0, 0, 4, 2))
.put_span((0, 1), &["ab", "cd"], Style::default());
}
assert_eq!(grid[Pos::new(0, 1)].glyph(), ' ');
let mut surface = screen(&mut grid);
surface
.clip(Rect::new(0, 0, 4, 2))
.put_span((0, 0), &["ab", "cd"], Style::default());
assert_eq!(grid[Pos::new(0, 0)].span(), (2, 2));
}
#[test]
fn clip_makes_put_span_uniform_measure_its_footprint_against_the_sub_rect() {
let mut grid = Grid::new(4, 3);
let style = Style::default();
{
let mut surface = screen(&mut grid);
let mut content = surface.clip(Rect::new(0, 0, 4, 2));
assert_eq!(
content.put_span_uniform((0, 1), (2, 2), 'C', '.', style),
None
);
assert_eq!(
content.put_span_uniform((0, 0), (2, 2), 'C', '.', style),
Some(())
);
}
assert_eq!(grid[Pos::new(0, 0)].span(), (2, 2));
assert_eq!(grid[Pos::new(0, 2)].glyph(), ' ');
}
#[test]
fn clip_to_a_disjoint_rect_is_empty_and_drops_every_write() {
let mut grid = Grid::new(8, 4);
{
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 4, 4), 0);
let mut sub = surface.clip(Rect::new(4, 0, 4, 4));
assert_eq!(sub.clip_rect(), Rect::EMPTY);
sub.print((0, 0), "abc", Style::default());
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn clip_to_a_zero_width_rect_is_empty_and_drops_every_write() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
let mut sub = surface.clip(Rect::new(1, 1, 0, 2));
assert_eq!(sub.clip_rect(), Rect::EMPTY);
sub.put((1, 1), 'X', Style::default());
}
assert_eq!(grid[Pos::new(1, 1)].glyph(), ' ');
}
#[test]
fn scope_to_a_zero_height_rect_is_empty_and_drops_every_write() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
let mut sub = surface.scope(Rect::new(1, 1, 2, 0));
assert_eq!(sub.area(), Rect::new(1, 1, 2, 0));
assert_eq!(sub.clip_rect(), Rect::EMPTY);
sub.put((0, 0), 'X', Style::default());
}
assert_eq!(grid[Pos::new(1, 1)].glyph(), ' ');
}
#[test]
fn print_with_an_empty_string_is_a_no_op() {
let mut grid = Grid::new(4, 4);
screen(&mut grid).print((0, 0), "", Style::default());
for y in 0..4 {
for x in 0..4 {
assert_eq!(grid[Pos::new(x, y)].glyph(), ' ', "cell ({x}, {y})");
}
}
}
#[test]
fn print_line_with_an_empty_line_is_a_no_op() {
let mut grid = Grid::new(4, 4);
screen(&mut grid).print_line((0, 0), &Line::default());
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn print_and_fill_rect_are_no_ops_on_a_surface_with_an_empty_area() {
let mut grid = Grid::new(4, 4);
{
let mut surface = Surface::new(&mut grid, Rect::EMPTY, 0);
surface.print((0, 0), "hi", Style::default());
surface.fill_rect(Rect::new(0, 0, 4, 4), '#', Style::default());
}
for y in 0..4 {
for x in 0..4 {
assert_eq!(grid[Pos::new(x, y)].glyph(), ' ', "cell ({x}, {y})");
}
}
}
#[test]
fn put_signed_drops_a_negative_coordinate() {
let mut grid = Grid::new(4, 4);
let mut surface = screen(&mut grid);
surface.put_signed((-1, 0), 'X', Style::default());
surface.put_signed((0, -1), 'X', Style::default());
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn put_signed_lands_a_valid_coordinate_at_the_area_origin() {
let mut grid = Grid::new(4, 4);
let area = Rect::new(1, 1, 2, 2);
let mut surface = Surface::new(&mut grid, area, 0);
surface.put_signed((0, 0), 'X', Style::default());
assert_eq!(grid[Pos::new(1, 1)].glyph(), 'X');
}
#[test]
fn put_signed_drops_a_coordinate_past_this_surfaces_width_or_height() {
let mut grid = Grid::new(4, 4);
let area = Rect::new(0, 0, 2, 2);
let mut surface = Surface::new(&mut grid, area, 0);
surface.put_signed((2, 0), 'X', Style::default());
surface.put_signed((0, 2), 'X', Style::default());
assert_eq!(grid[Pos::new(2, 0)].glyph(), ' ');
assert_eq!(grid[Pos::new(0, 2)].glyph(), ' ');
}
#[test]
fn put_signed_drops_a_coordinate_whose_shifted_offset_overflows_u16() {
let mut grid = Grid::new(4, 4);
let mut surface = screen(&mut grid);
surface.put_signed((100_000, 0), 'X', Style::default());
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn put_signed_drops_a_coordinate_inside_area_but_outside_a_narrower_clip() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface
.clip(Rect::new(0, 0, 2, 4))
.put_signed((3, 0), 'X', Style::default());
}
assert_eq!(grid[Pos::new(3, 0)].glyph(), ' ');
}
#[test]
fn put_signed_does_wide_char_bookkeeping_like_put() {
use crate::tile::TileFlags;
let mut grid = Grid::new(8, 2);
{
let mut surface = screen(&mut grid);
surface.put((0, 0), '\u{6f22}', Style::default());
surface.put_signed((0, 0), 'a', Style::default());
}
assert!(
!grid[Pos::new(1, 0)]
.flags()
.contains(TileFlags::WIDE_CHAR_SPACER)
);
screen(&mut grid).put_signed((3, 0), '\u{6f22}', Style::default());
assert!(grid[Pos::new(3, 0)].flags().contains(TileFlags::WIDE_CHAR));
assert!(
grid[Pos::new(4, 0)]
.flags()
.contains(TileFlags::WIDE_CHAR_SPACER)
);
}
#[test]
#[cfg(not(feature = "egc"))]
fn put_signed_does_not_tint_a_foreign_tile_when_put_tile_refuses_the_write() {
let mut grid = Grid::new(4, 1);
{
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 8, 1), 0);
surface
.with_tint(Tint::multiply(128, 64, 32))
.put_signed((3, 0), 'X', Style::default());
surface
.with_tint(Tint::multiply(1, 2, 3))
.put_signed((3, 0), '\u{6f22}', Style::default());
}
assert_eq!(grid[Pos::new(3, 0)].glyph(), 'X');
assert_eq!(grid.tint(0, 3, 0), Tint::multiply(128, 64, 32));
}
#[test]
fn put_offset_applies_the_surfaces_tint() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface.with_tint(Tint::multiply(128, 64, 32)).put_offset(
(1, 1),
Offset::new(2, -2),
'X',
Style::default(),
);
}
assert_eq!(grid.tint(0, 1, 1), Tint::multiply(128, 64, 32));
}
#[test]
fn put_offset_still_carries_the_pixel_offset() {
let mut grid = Grid::new(4, 4);
let mut surface = screen(&mut grid);
surface.put_offset((1, 1), Offset::new(2, -2), 'X', Style::default());
let tile = grid.tile(0, Pos::new(1, 1)).unwrap();
assert_eq!((tile.dx(), tile.dy()), (2, -2));
}
#[test]
#[cfg(feature = "egc")]
fn put_offset_does_not_move_a_pre_existing_tile_when_its_own_write_is_refused() {
let mut grid = Grid::new(8, 1);
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 8, 1), 0);
surface.put((3, 0), 'Z', Style::default());
surface.clip(Rect::new(0, 0, 4, 1)).put_offset(
(3, 0),
Offset::new(7, -7),
'\u{6f22}',
Style::default(),
);
let tile = grid.tile(0, Pos::new(3, 0)).unwrap();
assert_eq!(tile.glyph(), 'Z');
assert_eq!((tile.dx(), tile.dy()), (0, 0));
}
#[test]
#[cfg(not(feature = "egc"))]
fn put_offset_does_not_move_a_pre_existing_tile_when_its_own_write_is_refused() {
let mut grid = Grid::new(4, 1);
let mut surface = screen(&mut grid);
surface.put((3, 0), 'Z', Style::default());
surface.put_offset((3, 0), Offset::new(7, -7), '\u{6f22}', Style::default());
let tile = grid.tile(0, Pos::new(3, 0)).unwrap();
assert_eq!(tile.glyph(), 'Z');
assert_eq!((tile.dx(), tile.dy()), (0, 0));
}
#[test]
fn put_offset_on_a_refused_write_leaves_the_targets_glyph_and_offset_alone() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
let mut clipped = surface.clip(Rect::new(0, 0, 2, 2));
clipped.put((1, 1), 'X', Style::default());
clipped.put_offset((3, 3), Offset::new(5, -5), 'Y', Style::default());
}
let target = grid.tile(0, Pos::new(3, 3)).unwrap();
assert_eq!(target.glyph(), ' ');
assert_eq!((target.dx(), target.dy()), (0, 0));
assert_eq!(grid[Pos::new(1, 1)].glyph(), 'X');
}
#[test]
fn translate_does_not_change_area_width_or_height() {
let mut grid = Grid::new(10, 10);
let mut surface = screen(&mut grid);
let mut scoped = surface.scope(Rect::new(5, 5, 4, 4));
let view = scoped.translate((-5, -5));
assert_eq!(view.area(), Rect::new(5, 5, 4, 4));
assert_eq!(view.clip_rect(), Rect::new(5, 5, 4, 4));
assert_eq!(view.width(), 4);
assert_eq!(view.height(), 4);
}
#[test]
fn clip_translate_does_not_change_area_local_area_width_or_height() {
let mut grid = Grid::new(10, 10);
let mut surface = screen(&mut grid);
let view = surface.clip_translate(Rect::new(5, 5, 4, 4), (-5, -5));
assert_eq!(view.area(), Rect::new(5, 5, 4, 4));
assert_eq!(view.area().at_origin(), Rect::new(0, 0, 4, 4));
assert_eq!(view.width(), 4);
assert_eq!(view.height(), 4);
}
#[test]
fn translate_saturates_at_i32_max_across_repeated_calls_instead_of_overflowing() {
let mut grid = Grid::new(4, 4);
let mut surface = screen(&mut grid);
let mut once = surface.translate((i32::MAX - 1, 0));
let mut view = once.translate((5, 0));
view.put((0, 0), 'A', Style::default());
view.put((3, 3), 'B', Style::default());
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
assert_eq!(grid[Pos::new(3, 3)].glyph(), ' ');
}
#[test]
fn translate_saturates_at_i32_min_across_repeated_calls_instead_of_overflowing() {
let mut grid = Grid::new(4, 4);
let mut surface = screen(&mut grid);
let mut once = surface.translate((i32::MIN + 1, 0));
let mut view = once.translate((-5, 0));
view.put((0, 0), 'A', Style::default());
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
}
#[test]
fn translate_composes_with_scope_and_clip_rect() {
let mut grid = Grid::new(10, 10);
let mut surface = screen(&mut grid);
let mut clipped = surface.clip(Rect::new(0, 0, 6, 6));
let mut scoped = clipped.scope(Rect::new(4, 4, 4, 4));
let view = scoped.translate((-4, -4));
assert_eq!(view.area(), Rect::new(4, 4, 4, 4));
assert_eq!(view.clip_rect(), Rect::new(4, 4, 2, 2));
}
#[test]
fn translate_shifts_put_by_subtracting_the_origin() {
let mut grid = Grid::new(10, 10);
{
let mut surface = screen(&mut grid);
let mut view = surface.translate((3, 3));
view.put((3, 3), 'A', Style::default());
view.put((2, 3), 'B', Style::default());
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'A');
assert_eq!(grid[Pos::new(0, 3)].glyph(), ' ');
}
#[test]
fn translate_composes_with_scope_and_lets_a_negative_signed_coordinate_land() {
let mut grid = Grid::new(10, 10);
{
let mut surface = screen(&mut grid);
let mut scoped = surface.scope(Rect::new(5, 5, 4, 4));
let mut view = scoped.translate((-5, -5));
view.put_signed((-5, -5), 'X', Style::default());
view.put_signed((-6, -6), 'Y', Style::default());
}
assert_eq!(grid[Pos::new(5, 5)].glyph(), 'X');
assert_eq!(grid[Pos::new(4, 4)].glyph(), ' ');
}
#[test]
fn translate_composes_with_clip_and_shifts_put_the_same_way_as_put_signed() {
let mut grid = Grid::new(20, 20);
{
let mut surface = screen(&mut grid);
let mut view = surface.clip_translate(Rect::new(5, 5, 10, 10), (45, 45));
view.put((50, 50), '@', Style::default());
}
assert_eq!(grid[Pos::new(10, 10)].glyph(), '@');
assert_eq!(grid[Pos::new(5, 5)].glyph(), ' ');
}
#[test]
fn translate_composes_additively_across_two_calls() {
let mut grid = Grid::new(10, 10);
{
let mut surface = screen(&mut grid);
let mut once = surface.translate((2, 0));
let mut twice = once.translate((1, 0));
twice.put((3, 0), 'A', Style::default());
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'A');
}
#[test]
fn translate_shifts_fill_rect_print_and_clear_region_via_put() {
let mut grid = Grid::new(10, 10);
{
let mut surface = screen(&mut grid);
let mut view = surface.translate((5, 5));
view.fill_rect(Rect::new(5, 5, 2, 2), '#', Style::default());
view.print((5, 6), "a", Style::default());
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), '#');
assert_eq!(grid[Pos::new(1, 1)].glyph(), '#');
assert_eq!(grid[Pos::new(0, 1)].glyph(), 'a');
}
#[test]
fn translate_shifts_print_wrap_to_the_areas_own_width_not_the_local_offset_width() {
let mut grid = Grid::new(10, 4);
{
let mut surface = screen(&mut grid);
let mut view = surface.translate((5, 0));
view.print((5, 0), "abcdefghij", Style::default());
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(9, 0)].glyph(), 'j');
assert_eq!(grid[Pos::new(0, 1)].glyph(), ' ');
}
#[test]
fn translate_shifts_print_line_which_still_emits_its_spans() {
use crate::text::Span;
use alloc::vec;
let mut grid = Grid::new(4, 1);
{
let mut surface = screen(&mut grid);
let mut view = surface.translate((10, 0));
let line = Line::from(vec![Span::raw("ab"), Span::raw("cd")]);
view.print_line((10, 0), &line);
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(1, 0)].glyph(), 'b');
assert_eq!(grid[Pos::new(2, 0)].glyph(), 'c');
assert_eq!(grid[Pos::new(3, 0)].glyph(), 'd');
}
#[test]
fn translate_shifts_clear_region() {
let mut grid = Grid::new(10, 10);
{
let mut surface = screen(&mut grid);
surface.fill_rect(Rect::new(0, 0, 4, 4), '#', Style::default());
let mut view = surface.translate((2, 2));
view.clear_region(Rect::new(2, 2, 2, 2));
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
assert_eq!(grid[Pos::new(1, 1)].glyph(), ' ');
assert_eq!(grid[Pos::new(2, 2)].glyph(), '#');
}
#[test]
fn translate_shifts_put_span_and_put_span_uniform() {
let mut grid = Grid::new(10, 10);
{
let mut surface = screen(&mut grid);
let mut view = surface.translate((4, 4));
assert_eq!(view.put_span((4, 4), &["ab"], Style::default()), Some(()));
assert_eq!(
view.put_span_uniform((6, 4), (2, 1), 'C', ' ', Style::default()),
Some(())
);
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(2, 0)].glyph(), 'C');
}
#[test]
fn translate_shifts_print_and_still_wraps_at_the_surfaces_own_width() {
let mut grid = Grid::new(4, 4);
{
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 4, 2), 0);
let mut view = surface.translate((2, 0));
view.print((2, 0), "abcde", Style::default());
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(1, 0)].glyph(), 'b');
assert_eq!(grid[Pos::new(2, 0)].glyph(), 'c');
assert_eq!(grid[Pos::new(3, 0)].glyph(), 'd');
assert_eq!(grid[Pos::new(0, 1)].glyph(), 'e');
}
#[test]
fn translate_shifts_print_line() {
use crate::text::Span;
use alloc::vec;
let mut grid = Grid::new(6, 4);
{
let mut surface = screen(&mut grid);
let mut view = surface.translate((2, 0));
let line = Line::from(vec![Span::raw("ab"), Span::raw("cd")]);
view.print_line((2, 0), &line);
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'a');
assert_eq!(grid[Pos::new(1, 0)].glyph(), 'b');
assert_eq!(grid[Pos::new(2, 0)].glyph(), 'c');
assert_eq!(grid[Pos::new(3, 0)].glyph(), 'd');
}
#[test]
fn translate_composes_with_with_style() {
let mut grid = Grid::new(10, 10);
{
let mut surface = screen(&mut grid);
let mut view = surface.translate((3, 3));
let mut styled = view.with_style(Style::new().fg(Color::RED));
styled.put((3, 3), 'A');
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'A');
assert_eq!(grid[Pos::new(0, 0)].style().foreground(), Color::RED);
}
#[test]
fn clear_is_unaffected_by_translate() {
let mut grid = Grid::new(4, 4);
{
let mut surface = screen(&mut grid);
surface.fill_rect(Rect::new(0, 0, 4, 4), '#', Style::default());
let mut view = surface.translate((100, 100));
view.clear();
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
assert_eq!(grid[Pos::new(3, 3)].glyph(), ' ');
}
#[test]
fn clear_only_clears_the_intersection_of_area_and_clip() {
let mut grid = Grid::new(6, 6);
{
let mut surface = screen(&mut grid);
surface.fill_rect(Rect::new(0, 0, 6, 6), '#', Style::default());
surface.clip(Rect::new(2, 2, 2, 2)).clear();
}
assert_eq!(grid[Pos::new(2, 2)].glyph(), ' ');
assert_eq!(grid[Pos::new(3, 3)].glyph(), ' ');
assert_eq!(grid[Pos::new(0, 0)].glyph(), '#');
assert_eq!(grid[Pos::new(5, 5)].glyph(), '#');
}
#[test]
fn clear_on_a_scoped_surface_clears_the_scoped_area_intersected_with_the_parent_clip() {
let mut grid = Grid::new(6, 6);
{
let mut surface = screen(&mut grid);
surface.fill_rect(Rect::new(0, 0, 6, 6), '#', Style::default());
let mut clipped = surface.clip(Rect::new(0, 0, 4, 4));
clipped.scope(Rect::new(2, 2, 4, 4)).clear();
}
assert_eq!(grid[Pos::new(2, 2)].glyph(), ' ');
assert_eq!(grid[Pos::new(3, 3)].glyph(), ' ');
assert_eq!(grid[Pos::new(5, 5)].glyph(), '#');
assert_eq!(grid[Pos::new(0, 0)].glyph(), '#');
}
#[test]
fn grid_is_the_read_only_counterpart_of_grid_mut() {
let mut grid = Grid::new(4, 4);
let mut surface = screen(&mut grid);
surface.put((1, 1), 'X', Style::default());
assert_eq!(surface.grid()[Pos::new(1, 1)].glyph(), 'X');
}
#[test]
fn print_aligned_left_aligns_by_default() {
let mut grid = Grid::new(8, 1);
{
let mut surface = screen(&mut grid);
surface.print_aligned(
Rect::new(0, 0, 8, 1),
"hi",
crate::layout::HAlign::Left,
Style::default(),
);
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'h');
assert_eq!(grid[Pos::new(1, 0)].glyph(), 'i');
assert_eq!(grid[Pos::new(2, 0)].glyph(), ' ');
}
#[test]
fn print_aligned_centers_matching_text_layouts_own_saturating_formula() {
let mut grid = Grid::new(6, 1);
{
let mut surface = screen(&mut grid);
surface.print_aligned(
Rect::new(0, 0, 6, 1),
"hi",
crate::layout::HAlign::Center,
Style::default(),
);
}
assert_eq!(grid[Pos::new(2, 0)].glyph(), 'h');
assert_eq!(grid[Pos::new(3, 0)].glyph(), 'i');
}
#[test]
fn print_aligned_right_aligns_flush_to_the_rects_right_edge() {
let mut grid = Grid::new(6, 1);
{
let mut surface = screen(&mut grid);
surface.print_aligned(
Rect::new(0, 0, 6, 1),
"hi",
crate::layout::HAlign::Right,
Style::default(),
);
}
assert_eq!(grid[Pos::new(4, 0)].glyph(), 'h');
assert_eq!(grid[Pos::new(5, 0)].glyph(), 'i');
}
#[test]
fn print_aligned_does_not_panic_or_underflow_on_text_wider_than_the_rect() {
let mut grid = Grid::new(4, 1);
{
let mut surface = screen(&mut grid);
surface.print_aligned(
Rect::new(0, 0, 4, 1),
"hello",
crate::layout::HAlign::Center,
Style::default(),
);
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'h');
assert_eq!(grid[Pos::new(3, 0)].glyph(), 'l');
}
#[test]
fn print_aligned_clips_to_this_surfaces_own_area_as_well_as_rect() {
let mut grid = Grid::new(4, 1);
{
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 2, 1), 0);
surface.print_aligned(
Rect::new(0, 0, 4, 1),
"hi",
crate::layout::HAlign::Right,
Style::default(),
);
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), ' ');
assert_eq!(grid[Pos::new(1, 0)].glyph(), ' ');
}
#[test]
fn print_aligned_right_on_an_offset_surface_uses_area_local_coordinates() {
let mut grid = Grid::new(8, 1);
{
let mut surface = Surface::new(&mut grid, Rect::new(2, 0, 6, 1), 0);
surface.print_aligned(
Rect::new(0, 0, 6, 1),
"hi",
crate::layout::HAlign::Right,
Style::default(),
);
}
assert_eq!(grid[Pos::new(6, 0)].glyph(), 'h');
assert_eq!(grid[Pos::new(7, 0)].glyph(), 'i');
}
#[test]
fn print_aligned_center_on_an_offset_surface_uses_area_local_coordinates() {
let mut grid = Grid::new(8, 1);
{
let mut surface = Surface::new(&mut grid, Rect::new(2, 0, 6, 1), 0);
surface.print_aligned(
Rect::new(0, 0, 6, 1),
"hi",
crate::layout::HAlign::Center,
Style::default(),
);
}
assert_eq!(grid[Pos::new(4, 0)].glyph(), 'h');
assert_eq!(grid[Pos::new(5, 0)].glyph(), 'i');
}
#[test]
fn print_aligned_ignores_translate_and_still_lands_at_the_plain_local_position() {
let mut grid = Grid::new(10, 1);
{
let mut surface = Surface::new(&mut grid, Rect::new(0, 0, 10, 1), 0);
surface.translate((5, 0)).print_aligned(
Rect::new(0, 0, 10, 1),
"hi",
crate::layout::HAlign::Left,
Style::default(),
);
}
assert_eq!(grid[Pos::new(0, 0)].glyph(), 'h');
assert_eq!(grid[Pos::new(1, 0)].glyph(), 'i');
}
#[test]
fn clip_nests_monotonically() {
let mut grid = Grid::new(8, 4);
let mut surface = screen(&mut grid);
let mut outer = surface.clip(Rect::new(1, 1, 4, 2));
let inner = outer.clip(Rect::new(0, 0, 8, 4));
assert_eq!(inner.clip_rect(), Rect::new(1, 1, 4, 2));
}
#[test]
fn test_put_wide_char_sets_continuation() {
use crate::tile::TileFlags;
let mut term = Terminal::new(Headless::new(10, 3));
term.surface().put((0, 0), '\u{4e2d}', Style::default()); assert_eq!(term.grid()[Pos::new(0, 0)].glyph(), '\u{4e2d}');
assert!(
term.grid()[Pos::new(1, 0)]
.flags()
.contains(TileFlags::WIDE_CHAR_SPACER)
);
assert_eq!(term.grid()[Pos::new(1, 0)].glyph(), ' ');
assert_eq!(term.grid()[Pos::new(2, 0)].glyph(), ' '); }
#[test]
fn test_print_advances_by_char_width() {
use crate::tile::TileFlags;
let mut term = Terminal::new(Headless::new(10, 3));
term.surface().print((0, 0), "\u{4e2d}x", Style::default()); assert_eq!(term.grid()[Pos::new(0, 0)].glyph(), '\u{4e2d}');
assert!(
term.grid()[Pos::new(1, 0)]
.flags()
.contains(TileFlags::WIDE_CHAR_SPACER)
);
assert_eq!(term.grid()[Pos::new(2, 0)].glyph(), 'x');
}
#[test]
fn test_put_accepts_a_pos_or_a_tuple() {
let mut term = Terminal::new(Headless::new(10, 3));
let mut s = term.surface();
s.put(Pos::new(2, 1), 'X', Style::default());
s.put((3, 1), 'Y', Style::default());
assert_eq!(term.grid()[Pos::new(2, 1)].glyph(), 'X');
assert_eq!(term.grid()[Pos::new(3, 1)].glyph(), 'Y');
}
#[test]
fn test_put_offset_accepts_pos_and_offset_tuples() {
let mut term = Terminal::new(Headless::new(4, 1));
let mut s = term.surface();
s.put_offset(Pos::new(1, 0), Offset::new(3, -2), 'X', Style::default());
s.put_offset((2, 0), (-1, 4), 'Y', Style::default());
assert_eq!(term.grid()[Pos::new(1, 0)].glyph(), 'X');
assert_eq!(term.grid()[Pos::new(1, 0)].dx(), 3);
assert_eq!(term.grid()[Pos::new(1, 0)].dy(), -2);
assert_eq!(term.grid()[Pos::new(2, 0)].glyph(), 'Y');
assert_eq!(term.grid()[Pos::new(2, 0)].dx(), -1);
assert_eq!(term.grid()[Pos::new(2, 0)].dy(), 4);
}
#[test]
fn test_put_wide_char_at_last_column_does_not_overflow() {
let mut term = Terminal::new(Headless::new(4, 1));
term.surface().put((3, 0), '\u{4e2d}', Style::default()); assert_eq!(term.grid()[Pos::new(3, 0)].glyph(), ' '); }
#[test]
fn test_print_styled_basic() {
use crate::text::Span;
use alloc::vec;
let mut term = Terminal::new(Headless::new(20, 3));
let line = Line::from(vec![
Span::raw("HP: "),
Span::styled("100", Style::new().fg(Color::GREEN)),
]);
term.surface().print_line((0, 0), &line);
assert_eq!(term.grid()[Pos::new(0, 0)].glyph(), 'H');
assert_eq!(term.grid()[Pos::new(3, 0)].glyph(), ' ');
assert_eq!(term.grid()[Pos::new(4, 0)].glyph(), '1');
assert_eq!(term.grid()[Pos::new(4, 0)].style.fg, Color::GREEN);
assert_eq!(term.grid()[Pos::new(6, 0)].glyph(), '0');
}
#[test]
fn test_print_styled_wide_chars() {
use crate::tile::TileFlags;
use alloc::vec;
let mut term = Terminal::new(Headless::new(10, 3));
let line = Line::from(vec![crate::text::Span::raw("\u{4e2d}x")]);
term.surface().print_line((0, 0), &line);
assert_eq!(term.grid()[Pos::new(0, 0)].glyph(), '\u{4e2d}');
assert!(
term.grid()[Pos::new(1, 0)]
.flags()
.contains(TileFlags::WIDE_CHAR_SPACER)
);
assert_eq!(term.grid()[Pos::new(2, 0)].glyph(), 'x');
}
#[test]
fn test_print_str_styled_applies_style_to_every_cell() {
let mut term = Terminal::new(Headless::new(20, 3));
term.surface()
.print((0, 0), "HP", Style::new().fg(Color::GREEN));
assert_eq!(term.grid()[Pos::new(0, 0)].glyph(), 'H');
assert_eq!(term.grid()[Pos::new(0, 0)].style.fg, Color::GREEN);
assert_eq!(term.grid()[Pos::new(1, 0)].glyph(), 'P');
assert_eq!(term.grid()[Pos::new(1, 0)].style.fg, Color::GREEN);
}