Skip to Content
Test an app

Test an app

Test what your application promises, at the narrowest boundary that proves it. Noir does not export a widget-test harness, so application tests should not import repository helpers or anything under package:noir/src/.

dart pub add --dev test dart test

Choose the boundary

What you need to proveUse
A rule, command, controller, or state transitionA plain Dart unit test
The public app tree can mount and disposerunTuiApp(..., headless: true)
A terminal accepts raw input or an escape protocolA check in the target terminal

This split keeps application tests fast and independent of terminal rendering.

Test the behavior you own

Move an application command into a small state owner, then test that owner without mounting a widget tree:

import 'package:noir/noir.dart'; import 'package:test/test.dart'; final class CounterController extends ValueNotifier<int> { CounterController() : super(0); void increment() { value++; } } void main() { test('increment advances the count', () { final counter = CounterController(); addTearDown(counter.dispose); counter.increment(); expect(counter.value, 1); }); }

The Button in the application calls counter.increment. The test covers the promised state change without knowing how the Button is painted or which key activated it. Use the same boundary for validation, asynchronous state, and commands invoked from keyboard, pointer, paste, or widget callbacks.

Mount the public lifecycle when it is the behavior

Use a headless app check when construction, inherited dependencies, or deterministic disposal is part of what you promise:

import 'package:noir/noir.dart'; import 'package:test/test.dart'; void main() { test('mounts and disposes headlessly', () { final app = runTuiApp(const Text('ready'), headless: true); expect(app.isHeadless, isTrue); app.dispose(); }); }

Headless mode mounts Noir’s normal widget and element tree without an owned terminal renderer. It proves that the tree builds and tears down. It does not show a rendered frame, exercise raw-mode stdin, or prove that a terminal accepts an escape sequence.

Always dispose the app handle, focus nodes, editing and scroll controllers, notifiers, subscriptions, and animation controllers in the layer that created them.

What a passing test does not prove

  • A headless mount is not evidence of raw-mode input, alternate-screen teardown, or protocol support. See Platform support.
  • Terminal images, OSC52 clipboard behavior, and enhanced keyboard reporting depend on the reader’s terminal. Check those in the terminal you target.

Contributing to Noir itself

The repository has four framework test compositions and a live-process driver. They are deliberately absent from the published package, so an application cannot import them. If you are changing the framework, read CONTRIBUTING.md  and the test helper guide .

Last updated on