# `Harlock.Test`
[🔗](https://github.com/thatsme/harlock/blob/v0.2.0/lib/harlock/test.ex#L1)

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)
    end

Anything 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.

# `handle`

```elixir
@type handle() :: %{
  sup: pid(),
  name: atom(),
  writer: atom(),
  reader: atom(),
  runtime: atom()
}
```

# `cells`

```elixir
@spec cells(handle()) :: Harlock.Render.Buffer.t()
```

Returns the current cell buffer struct.

# `cursor`

```elixir
@spec cursor(handle()) :: {non_neg_integer(), non_neg_integer()} | nil
```

Returns the current Frame.cursor — {row, col} or nil if hidden.

# `focused`

```elixir
@spec focused(handle()) :: any()
```

Returns the id of the currently focused element, or nil.

# `model`

```elixir
@spec model(handle()) :: any()
```

Returns the current model from the runtime.

# `quit?`

```elixir
@spec quit?(handle()) :: boolean()
```

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`.

# `raw_writes`

```elixir
@spec raw_writes(handle()) :: binary()
```

Returns the raw bytes the writer has received so far.

# `render`

```elixir
@spec render(handle()) :: String.t()
```

Returns the rendered cell buffer as a single string (rows joined by \n).

# `resize`

```elixir
@spec resize(handle(), pos_integer(), pos_integer()) :: :ok
```

Inject a resize event. Stand-in for SIGWINCH in headless tests — the
runtime updates its dimensions, discards `prev_frame`, and re-renders.
The test writer's cell buffer is resized first so the new frame has
somewhere to land.

# `send_event`

```elixir
@spec send_event(handle(), any()) :: :ok
```

Inject a synthetic event into the runtime. The event uses the same shape
the input parser emits (e.g. `{:key, :tab, []}`).

# `send_key`

```elixir
@spec send_key(handle(), any(), [atom()]) :: :ok
```

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])

# `start_app`

```elixir
@spec start_app(module(), any(), keyword()) :: handle()
```

Start an app under the test backend and return a handle for the helpers
in this module.

# `stop`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
