#![allow(dead_code)]
pub fn strip_escapes(bytes: &[u8]) -> String {
let mut out = Vec::new();
let mut i = 0;
while i < bytes.len() {
if bytes[i] != 0x1b {
out.push(bytes[i]);
i += 1;
continue;
}
i += 1;
match bytes.get(i) {
Some(b'[') => {
i += 1;
while i < bytes.len() && !(0x40..=0x7e).contains(&bytes[i]) {
i += 1;
}
i += 1;
}
Some(b']') => {
while i < bytes.len()
&& bytes[i] != 0x07
&& !(bytes[i] == 0x1b && bytes.get(i + 1) == Some(&b'\\'))
{
i += 1;
}
i += if bytes.get(i) == Some(&0x07) { 1 } else { 2 };
}
_ => i += 1,
}
}
String::from_utf8_lossy(&out).replace("\r\n", "\n")
}
macro_rules! e2e_scenarios {
() => {
fn started() -> Pty {
let pty = Pty::spawn(&example("events"));
pty.expect("q or Ctrl+C quits");
pty
}
#[test]
fn the_example_starts_in_a_real_console() {
started();
}
#[test]
fn paint_draws_its_screen_with_terminal_draw_then_quits_cleanly() {
let pty = Pty::spawn(&example("paint"));
pty.expect("q: quit");
pty.send(b"\x1b[<0;10;5M\x1b[<32;20;5M\x1b[<0;20;5m");
pty.send(b"q");
assert_eq!(pty.exit_code(), 0);
}
#[test]
fn the_screensaver_animates_by_itself_and_any_key_quits() {
let pty = Pty::spawn(&example("screensaver"));
pty.expect("█");
pty.send(b"x");
assert_eq!(pty.exit_code(), 0);
}
#[test]
fn the_plasma_sketch_fills_the_window_reacts_to_the_mouse_and_quits() {
let pty = Pty::spawn(&example("plasma"));
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
while !pty.text().contains(['░', '▒', '▓', '█']) {
assert!(
std::time::Instant::now() < deadline,
"nothing was drawn; the program printed {:?}",
pty.text()
);
std::thread::sleep(std::time::Duration::from_millis(20));
}
pty.send(b"\x1b[<35;30;10M\x1b[<35;60;5M\x1b[<0;60;5M\x1b[<32;70;8M\x1b[<0;70;8m");
std::thread::sleep(std::time::Duration::from_millis(300));
assert!(!pty.has_exited(), "mouse input stopped the sketch");
pty.send(b"q");
assert_eq!(pty.exit_code(), 0);
}
#[test]
fn the_other_art_examples_each_draw_react_to_the_mouse_and_quit() {
for name in ["rings", "tunnel", "waves", "bubbles"] {
let pty = Pty::spawn(&example(name));
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
while !pty.text().contains(['░', '▒', '▓', '█']) {
assert!(
std::time::Instant::now() < deadline,
"{name} drew nothing; it printed {:?}",
pty.text()
);
std::thread::sleep(std::time::Duration::from_millis(20));
}
pty.send(b"\x1b[<35;30;10M\x1b[<0;40;8M\x1b[<32;50;9M\x1b[<0;50;9m\x1b[<2;20;5M");
std::thread::sleep(std::time::Duration::from_millis(300));
assert!(!pty.has_exited(), "{name} stopped on its own");
pty.send(b"q");
assert_eq!(pty.exit_code(), 0, "{name}");
}
}
#[test]
fn the_physics_world_draws_balls_takes_mouse_input_and_quits() {
let pty = Pty::spawn(&example("balls"));
pty.expect("█");
for i in 0..4 {
let x = 20 + i * 20;
pty.send(format!("\x1b[<0;{x};8M\x1b[<0;{x};8m").as_bytes());
pty.send(b"\x1b[<35;100;12M\x1b[<2;100;12M");
std::thread::sleep(std::time::Duration::from_millis(120));
pty.send(b"\x1b[<2;100;12m");
}
std::thread::sleep(std::time::Duration::from_millis(600));
assert!(!pty.has_exited(), "the world stopped on its own");
pty.send(b"q");
assert_eq!(pty.exit_code(), 0);
}
#[test]
fn the_quickstart_from_llms_txt_marks_a_clicked_cell_and_quits() {
let pty = Pty::spawn(&example("quickstart"));
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
while !pty.text().contains('X') {
assert!(
std::time::Instant::now() < deadline,
"no X appeared; the program printed {:?}",
pty.text()
);
pty.send(b"\x1b[<0;10;5M");
std::thread::sleep(std::time::Duration::from_millis(100));
}
pty.send(b"q");
assert_eq!(pty.exit_code(), 0);
}
#[test]
fn pasted_text_arrives_as_one_paste_event_not_key_presses() {
let pty = started();
pty.send(b"\x1b[200~cargo run\x1b[201~");
pty.expect("Paste(\"cargo run\")");
assert!(
!pty.text().contains("Char('"),
"the pasted letters must not arrive as key presses: {:?}",
pty.text()
);
}
#[test]
fn pasting_a_q_does_not_quit_paint_but_typing_one_does() {
let pty = Pty::spawn(&example("paint"));
pty.expect("q: quit");
pty.send(b"\x1b[200~cargo run q 7 c\x1b[201~");
std::thread::sleep(std::time::Duration::from_millis(400));
assert!(!pty.has_exited(), "a pasted q quit the program");
pty.send(b"q");
assert_eq!(pty.exit_code(), 0);
}
#[test]
fn the_window_size_is_found_by_asking_the_terminal() {
let pty = started();
pty.expect(&format!("size: {PTY_COLUMNS}x{PTY_ROWS}"));
}
#[test]
fn a_key_arrives_immediately_without_pressing_enter() {
let pty = started();
pty.send(b"a");
pty.expect("Char('a')");
}
#[test]
fn input_is_not_echoed_by_the_console() {
let pty = started();
pty.send(b"zzz");
pty.expect("Char('z')");
assert!(!pty.text().contains("zzz"), "{:?}", pty.text());
}
#[test]
fn a_mouse_click_is_parsed_with_zero_based_coordinates() {
let pty = started();
pty.send(b"\x1b[<0;12;5M");
pty.expect("kind: Down(Left), x: 11, y: 4");
}
#[test]
fn a_mouse_drag_and_release_are_reported() {
let pty = started();
pty.send(b"\x1b[<32;3;4M\x1b[<0;3;4m");
pty.expect("kind: Drag(Left), x: 2, y: 3");
pty.expect("kind: Up(Left), x: 2, y: 3");
}
#[test]
fn arrow_keys_arrive_as_arrow_events() {
let pty = started();
pty.send(b"\x1b[A");
pty.expect("code: Up");
}
#[test]
fn q_quits_with_a_zero_exit_code() {
let pty = started();
pty.send(b"q");
assert_eq!(pty.exit_code(), 0);
}
#[test]
fn ctrl_c_is_delivered_as_a_key_and_the_program_exits_cleanly() {
let pty = started();
pty.send(&[0x03]);
pty.expect("ctrl: true");
assert_eq!(pty.exit_code(), 0);
}
};
}