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 testChoose the boundary
| What you need to prove | Use |
|---|---|
| A rule, command, controller, or state transition | A plain Dart unit test |
| The public app tree can mount and dispose | runTuiApp(..., headless: true) |
| A terminal accepts raw input or an escape protocol | A 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 .