tui4s
Getting started

The first test

Test the Counter on the headless test kit, with no terminal.
CounterSuite.scala
import kyo.*
import kyo.UI.Keyboard
import munit.FunSuite
import tui4s.testkit.Headless

/** The Counter on the headless test kit: no terminal. */
class CounterSuite extends FunSuite:

    /** Runs `v` to its end on the Kyo scheduler (at most 30 s). */
    private def run[A](v: => A < (Async & Scope))(using Frame): A =
        import AllowUnsafe.embrace.danger
        KyoApp.Unsafe.runAndBlock(30.seconds)(v).getOrThrow

    test("+ and - change the count") {
      val s = run {
        for
            h <- Headless.start(50, 8)(Counter.ui)
            _ <- h.char('+')
            _ <- h.char('+')
            _ <- h.char('-')
            s <- h.screen
        yield s
      }
      assert(s.contains("Count: 1"), s.text)
    }

Headless.start(50, 8) runs the UI on a screen of 50 columns and 8 rows, with no terminal. char, key, press, typeText and paste send input, and each one waits for the frame of its input. screen gives the current screen. Screen.contains finds a text, and Screen.text gives all the rows.

The test runs on munit here. The test kit does not need munit: it gives Kyo values, so any test framework that can run a Kyo value can use it. tui4s itself uses kyo-test for its own suites.

Run the tests of the examples:

sbt examples/test

Testing shows a test that waits for a signal, and the checks in a real terminal.

Copyright © 2026