class Termisu::Testing::Terminal

Overview

High-level E2E harness: spawns a program on a PTY, continuously emulates its output into a Screen, and exposes assertion helpers that mirror JS terminal test runners (#get_by_text, get_cursor, snapshot) — with auto-retrying waits so tests don't sprinkle fixed sleeps.

Prefer the block form which guarantees cleanup:

Termisu::Testing.terminal("./bin/app", cols: 100, rows: 50) do |t|
  t.get_by_text("Ready").should be_true
  t.write("q")
end

Defined in:

termisu/testing/terminal.cr

Constant Summary

KEYS = { up: "\e[A", down: "\e[B", right: "\e[C", left: "\e[D", enter: "\r", esc: "\e", tab: "\t", backspace: "\u007F", space: " ", home: "\e[H", end: "\e[F", page_up: "\e[5~", page_down: "\e[6~", }

Byte sequences for common special keys (xterm/standard).

Constructors

Class Method Summary

Instance Method Summary

Constructor Detail

def self.new(program : String, args : Array(String) = [] of String, *, cols : Int32 = 100, rows : Int32 = 50, env : Hash(String, String) | Nil = nil) #

[View source]

Class Method Detail

def self.terminal(program : String, args : Array(String) = [] of String, *, cols : Int32 = 100, rows : Int32 = 50, env : Hash(String, String) | Nil = nil, &) #

Spawns program, yields the harness, and always cleans up.


[View source]

Instance Method Detail

def close : Nil #

Stops the program and releases the PTY. Idempotent.


[View source]
def cols : Int32 #

[View source]
def ctrl(char : Char) : Nil #

Sends a Ctrl+ combination (e.g. #ctrl('c') → 0x03).


[View source]
def cursor : Tuple(Int32, Int32) #

Cursor position once the screen settles.


[View source]
def cursor_visible? : Bool #

Whether the cursor is currently visible (waits for the screen to settle).


[View source]
def exited? : Bool #

Whether the program has exited.


[View source]
def find(pattern : String | Regex) : Tuple(Int32, Int32) | Nil #

Current {x, y} of pattern right now, without waiting (returns nil if absent). Use for polling a moving element; use #locate to wait for it to appear. Reads @screen under the same lock the reader fiber writes with.


[View source]
def get_by_text(pattern : String | Regex, timeout : Time::Span = 3.seconds) : Bool #

Waits (up to timeout) until pattern appears on screen. Returns whether it became visible — assert with .should be_true.


[View source]
def key(name : Symbol) : Nil #

Sends a named special key (see KEYS).


[View source]
def key_press(char : Char) : Nil #

Sends a single character.


[View source]
def locate(pattern : String | Regex, timeout : Time::Span = 3.seconds) : Tuple(Int32, Int32) | Nil #

Waits for pattern to appear, then returns its first {x, y} (or nil).


[View source]
def resize(cols : Int32, rows : Int32) : Nil #

Resizes the terminal (delivers SIGWINCH to the child).


[View source]
def row(y : Int32) : String #

The text of screen row y once the screen settles.


[View source]
def rows : Int32 #

[View source]
def screen : Screen #

[View source]
def snapshot(mask : Array(Regex) = [] of Regex) : String #

Styled snapshot of the rendered screen (glyph grid + per-cell fg/bg/attr). mask blanks volatile regions (matched per row) so animated screens are deterministic.


[View source]
def wait_stable(quiet_for : Time::Span = 80.milliseconds, timeout : Time::Span = 3.seconds) : Bool #

Blocks until the program has produced no new output for quiet_for (render-stable), or timeout elapses. Returns whether the screen actually stabilized: animated screens (a spinner, a moving element) never quiesce and return false on timeout by design — callers mask the volatile region rather than rely on stability.


[View source]
def wait_until(timeout : Time::Span = 3.seconds, &) : Bool #

Blocks until block returns true, or timeout elapses. Yields to the reader fiber between checks (no busy spin).


[View source]
def write(data : String) : Nil #

Sends raw text to the program.


[View source]