Test harness for Harlock apps.
Boots an app under a :test backend — no /dev/tty required — then
exposes synchronous helpers for driving events and inspecting state.
test "tab moves focus to next field" do
h = Harlock.Test.start_app(MyApp, %{...})
Harlock.Test.send_key(h, :tab)
assert Harlock.Test.focused(h) == :email_field
Harlock.Test.stop(h)
endAnything that requires a real terminal lives behind the backend abstraction; everything tested here is the same code path that runs in production except for the bytes-in/bytes-out boundary.
Summary
Functions
Returns the current cell buffer struct.
Returns the current Frame.cursor — {row, col} or nil if hidden.
Returns the id of the currently focused element, or nil.
Returns the current model from the runtime.
Returns true if the app has signaled :quit from its update callback.
The runtime exits but the supervisor stays alive until you call stop/1.
Returns the raw bytes the writer has received so far.
Returns the rendered cell buffer as a single string (rows joined by \n).
Inject a resize event. Stand-in for SIGWINCH in headless tests — the runtime updates its dimensions, clears the screen, and redraws at the new size. The test writer's cell buffer is resized first so the new frame has somewhere to land.
Inject a synthetic event into the runtime. The event uses the same shape
the input parser emits (e.g. {:key, :tab, []}).
Convenience for {:key, key, mods} events.
Convenience for mouse events, in the shape the input parser emits. col and
row are 1-indexed, as the terminal reports them. The app must be started
with mouse: true for them to be routed.
Start an app under the test backend and return a handle for the helpers in this module.
Stop the app cleanly. Safe to call after the app has already quit on its
own (e.g. via update returning :quit). Drains any pending
:harlock_done message from the calling process's mailbox.
Types
Functions
@spec cells(handle()) :: Harlock.Render.Buffer.t()
Returns the current cell buffer struct.
@spec cursor(handle()) :: {non_neg_integer(), non_neg_integer()} | nil
Returns the current Frame.cursor — {row, col} or nil if hidden.
Returns the id of the currently focused element, or nil.
Returns the current model from the runtime.
Returns true if the app has signaled :quit from its update callback.
The runtime exits but the supervisor stays alive until you call stop/1.
Returns the raw bytes the writer has received so far.
Returns the rendered cell buffer as a single string (rows joined by \n).
@spec resize(handle(), pos_integer(), pos_integer()) :: :ok
Inject a resize event. Stand-in for SIGWINCH in headless tests — the runtime updates its dimensions, clears the screen, and redraws at the new size. The test writer's cell buffer is resized first so the new frame has somewhere to land.
Inject a synthetic event into the runtime. The event uses the same shape
the input parser emits (e.g. {:key, :tab, []}).
Convenience for {:key, key, mods} events.
Harlock.Test.send_key(h, :tab)
Harlock.Test.send_key(h, {:char, ?q})
Harlock.Test.send_key(h, :enter, [:ctrl])
@spec send_mouse(handle(), atom(), atom() | nil, pos_integer(), pos_integer(), [ atom() ]) :: :ok
Convenience for mouse events, in the shape the input parser emits. col and
row are 1-indexed, as the terminal reports them. The app must be started
with mouse: true for them to be routed.
Harlock.Test.send_mouse(h, :press, :left, 5, 2)
Harlock.Test.send_mouse(h, :wheel_down, nil, 5, 2)
Start an app under the test backend and return a handle for the helpers in this module.
Options:
:rows,:cols— the size of the test screen (default 24×80).:name— base name for the app's processes (default: a unique one), useful when a test needs to address them.:theme— as forHarlock.run/3.:exec— stands in for the programsHarlock.Cmd.exec/3would run, since there is no terminal to hand over. A function receiving the program, args and options the app passed, returning the result the app should get:h = Harlock.Test.start_app(Editor, nil, exec: fn "vim", [path], _opts -> send(test_pid, {:edited, path}) {:ok, 0} end)Without it, every exec returns
{:error, :no_terminal}.:mouse— route mouse events asHarlock.run/3does withmouse: true, forsend_mouse/6.:suspend— stands in forHarlock.Cmd.suspend/0in the same way: a 0-arity function returning the result the app should get, typically{:ok, :resumed}. Without it, suspend returns{:error, :no_terminal}.
@spec stop(handle()) :: :ok
Stop the app cleanly. Safe to call after the app has already quit on its
own (e.g. via update returning :quit). Drains any pending
:harlock_done message from the calling process's mailbox.