use std::time::Duration;
use termlens::{GraphicsAction, GraphicsFormat, GraphicsProtocol, Screen, Terminal};
mod common;
use common as util;
fn spawn(mode: &str) -> termlens::Result<Terminal> {
Terminal::builder()
.size(40, 10)
.timeout(Duration::from_secs(30))
.env_clear()
.arg(mode)
.spawn(util::fixture_bin("image-echo"))
}
fn drawn(screen: &Screen) -> bool {
screen.contains("done")
}
fn run(mode: &str) -> termlens::Result<(Terminal, Screen)> {
let mut terminal = spawn(mode)?;
terminal.wait_until(drawn)?;
let screen = terminal.screen();
Ok((terminal, screen))
}
#[test]
fn a_transmitted_image_is_counted_and_described() -> termlens::Result<()> {
let (_terminal, screen) = run("kitty")?;
let seen = screen.graphics();
assert_eq!(seen.kitty(), 1, "one image: {seen:?}");
assert_eq!(seen.sixel(), 0);
assert_eq!(seen.deletes(), 0);
assert!(!seen.is_empty());
let image = seen.last().expect("a payload");
assert_eq!(image.protocol(), GraphicsProtocol::Kitty);
assert_eq!(image.action(), GraphicsAction::TransmitAndPlace);
assert_eq!(image.format(), GraphicsFormat::Rgba);
assert!(image.compressed(), "the fixture zlib's it");
assert_eq!(image.id(), Some(7));
assert_eq!(image.size(), Some((4, 2)), "the size it declared");
assert_eq!(image.cells(), Some((2, 1)), "pinned to the cells reserved");
assert!(image.bytes() > 0);
Ok(())
}
#[test]
fn an_image_is_stamped_with_the_cell_it_was_placed_on() -> termlens::Result<()> {
for mode in ["kitty", "sixel"] {
let (_terminal, screen) = run(mode)?;
let seen = screen.graphics();
let image = seen.last().expect("a payload");
assert_eq!(image.at(), (1, 4), "{mode} landed at {:?}", image.at());
}
Ok(())
}
#[test]
fn a_chunked_transmission_is_one_image_not_three() -> termlens::Result<()> {
let (_terminal, chunked) = run("kitty-chunks")?;
let (_terminal, whole) = run("kitty-plain")?;
let chunked_seen = chunked.graphics();
let whole_seen = whole.graphics();
assert_eq!(chunked_seen.kitty(), 1, "{chunked_seen:?}");
assert_eq!(whole_seen.kitty(), 1, "{whole_seen:?}");
let split = chunked_seen.last().expect("a payload");
let single = whole_seen.last().expect("a payload");
assert!(split.chunks() > 1, "the fixture split it: {split:?}");
assert_eq!(single.chunks(), 1);
assert_eq!(split.data(), single.data());
assert_eq!(split.size(), single.size());
Ok(())
}
#[test]
fn a_delete_is_not_an_image_transmitted() -> termlens::Result<()> {
let (_terminal, screen) = run("delete")?;
let seen = screen.graphics();
assert_eq!(seen.kitty(), 1, "one image: {seen:?}");
assert_eq!(seen.deletes(), 1, "and one teardown: {seen:?}");
assert_eq!(seen.total(), 1);
let payloads = seen.payloads();
assert_eq!(
payloads.len(),
2,
"both are still on the wire: {payloads:?}"
);
assert_eq!(payloads[1].action(), GraphicsAction::Delete);
assert_eq!(payloads[1].id(), Some(7));
assert!(!payloads[1].action().carries_image());
Ok(())
}
#[test]
fn a_program_that_draws_no_image_reports_none() -> termlens::Result<()> {
let (_terminal, screen) = run("none")?;
let seen = screen.graphics();
assert!(seen.is_empty(), "{seen:?}");
assert_eq!(seen.bytes(), 0);
assert!(seen.payloads().is_empty());
assert!(seen.last().is_none());
Ok(())
}
#[test]
fn the_image_leaves_the_text_around_it_alone() -> termlens::Result<()> {
let (_terminal, screen) = run("kitty")?;
assert!(screen.contains("label above"), "{screen}");
assert!(screen.contains("label below"), "{screen}");
assert_eq!(screen.row_text(1).trim(), "", "{screen}");
Ok(())
}
#[test]
fn a_bounded_capture_keeps_the_counts_and_drops_the_bytes() -> termlens::Result<()> {
let mut terminal = Terminal::builder()
.size(40, 10)
.timeout(Duration::from_secs(30))
.env_clear()
.capture_graphics(0)
.arg("kitty")
.spawn(util::fixture_bin("image-echo"))?;
terminal.wait_until(drawn)?;
let screen = terminal.screen();
let seen = screen.graphics();
assert_eq!(seen.kitty(), 1, "still counted: {seen:?}");
let image = seen.last().expect("a payload");
assert_eq!(image.size(), Some((4, 2)), "still described");
assert!(image.bytes() > 0, "still measured");
assert_eq!(image.data(), None, "and not kept");
Ok(())
}
#[cfg(feature = "decode")]
mod decoded {
use super::*;
use termlens::DecodeError;
const GREEN: [u8; 4] = [0x39, 0xd3, 0x53, 0xff];
const BLUE: [u8; 4] = [0x00, 0x00, 0xff, 0xff];
#[test]
fn a_kitty_image_decodes_into_the_pixels_that_were_drawn() -> termlens::Result<()> {
for mode in ["kitty", "kitty-plain", "kitty-chunks"] {
let (_terminal, screen) = run(mode)?;
let seen = screen.graphics();
let bitmap = seen
.last()
.expect("a payload")
.decode()
.unwrap_or_else(|error| panic!("{mode}: {error}"));
assert_eq!((bitmap.width(), bitmap.height()), (4, 2), "{mode}");
assert_eq!(bitmap.pixel(0, 0), Some(GREEN), "{mode}");
assert_eq!(bitmap.pixel(1, 1), Some(GREEN), "{mode}");
assert_eq!(bitmap.pixel(2, 0), Some(BLUE), "{mode}");
assert_eq!(bitmap.pixel(3, 0), Some([0, 0, 0, 0]), "{mode}");
assert_eq!(bitmap.pixel(4, 0), None, "out of bounds is None");
let colours = bitmap.colours();
assert_eq!(colours[0], (GREEN, 4), "{mode}: {colours:?}");
}
Ok(())
}
#[test]
fn a_sixel_decodes_into_the_pixels_that_were_painted() -> termlens::Result<()> {
let (_terminal, screen) = run("sixel")?;
let seen = screen.graphics();
let image = seen.last().expect("a payload");
assert_eq!(image.protocol(), GraphicsProtocol::Sixel);
assert_eq!(image.format(), GraphicsFormat::Sixel);
assert_eq!(image.size(), Some((4, 2)), "from its raster attributes");
let bitmap = image.decode().expect("decodes");
assert_eq!((bitmap.width(), bitmap.height()), (4, 2));
assert_eq!(bitmap.pixel(0, 0), Some([0, 255, 0, 255]));
assert_eq!(bitmap.pixel(1, 1), Some([0, 255, 0, 255]));
assert_eq!(bitmap.pixel(2, 0), Some([0, 0, 255, 255]));
assert_eq!(bitmap.pixel(3, 0), Some([0, 0, 0, 0]));
Ok(())
}
#[test]
fn a_delete_refuses_to_decode_and_says_why() -> termlens::Result<()> {
let (_terminal, screen) = run("delete")?;
let seen = screen.graphics();
let delete = &seen.payloads()[1];
assert_eq!(
delete.decode().unwrap_err(),
DecodeError::NoImage(GraphicsAction::Delete)
);
Ok(())
}
#[test]
fn an_uncaptured_payload_names_the_bound_rather_than_guessing() -> termlens::Result<()> {
let mut terminal = Terminal::builder()
.size(40, 10)
.timeout(Duration::from_secs(30))
.env_clear()
.capture_graphics(0)
.arg("kitty")
.spawn(util::fixture_bin("image-echo"))?;
terminal.wait_until(drawn)?;
let screen = terminal.screen();
let seen = screen.graphics();
assert_eq!(
seen.last().expect("a payload").decode().unwrap_err(),
DecodeError::NotCaptured
);
Ok(())
}
}