greenroom 0.1.0
greenroom: ^0.1.0 copied to clipboard
Turns Flutter's own @Preview annotations into widget tests proving every preview mounts — with screenshots and a machine-readable verdicts file.
greenroom #
A code generator that turns Flutter's own @Preview annotations into
widget tests proving every preview mounts — plus screenshots and a
machine-readable verdicts file as test artifacts. A candidate version of
your app waits in the green room; it goes on stage only when it's green.
$ dart run greenroom # (re)generate test/greenroom.g_test.dart
$ flutter test # the gate: green = every preview mounts
No config file, no runtime dependency, no annotations of its own — the
@Preview annotation (package:flutter/widget_previews.dart) is the
configuration, and the generated test file is fully self-contained.
What gets generated #
One testWidgets per @Preview annotation (stacked annotations = one test
each), honoring the annotation's own fields:
name:→ test descriptionsize: Size(w, h)→ applied EXACTLY as the official previewer does (verified against flutter_tools' widget_preview_scaffold): a SizedBox constraint plus a MediaQueryData.size override, infinity = unconstrainedtextScaleFactor:→ MediaQuery text scalingbrightness:→ MediaQuery platform brightnesswrapper:→ applied around the entry (same-file public functions /Class.staticMember— the parse-only reach; anything else mounts unwrapped with a visible note)
Plus: bare mounts for public widgets with a no-required-param default
constructor and no entry of their own; failing obligation tests for
widget classes that can be neither entered nor bare-mounted ("add a
@Preview entry, e.g. …"); a PNG screenshot per passing test under
.dart_tool/greenroom/shots/; and a tearDownAll that writes
.dart_tool/greenroom/verdicts.json (per-entry ok/error/attribution/hint,
hash-stamped).
Everything mounts under your project shell when lib/preview/shell.dart
exports Widget shell(Widget child) — the app's real ambient wrappers plus
whatever fake services your previews need — else a bare MaterialApp.
What a failure looks like #
UnimplementedError: LiveUserService not wired — missing fake?
at package:example_app/services/user_service.dart:13:7
fix: Register a fixture-returning fake in lib/preview/world.dart.
The first project stack frame — or, for layout errors whose throw stacks
are framework-only, the offending widget's CREATION location
(--track-widget-creation) — plus a classified fix hint (missing fake,
unbounded viewport, missing provider/ambient, overflow). Overflow IS a
mount failure: A RenderFlex overflowed… fails the test, attributed to
the widget's line.
CI #
$ dart run greenroom --check && flutter test
--check regenerates in memory and fails (exit 1) if the committed file is
stale — the same drift-guard pattern as any committed codegen. Commit the
generated file.
Provenance & scope #
An annotation counts only if the file imports
package:flutter/widget_previews.dart under a matching spelling (bare,
as p prefixed, show/hide respected) — name-squatting Preview classes
are ignored (direct import required; re-exports are not recognized). The
same annotations remain fully consumable by Flutter's interactive
flutter widget-preview start; greenroom is its missing verification
half — checked 2026-08: the official tool has no test/headless mode, and
no ecosystem package generates tests from @Preview.
Lab modes (off the product path) #
dart run greenroom watch <dir>— a resident flutter-tester that re-verifies every entry on save (~180–380ms edit→verdict) and writes hash-gated verdicts;plugin/surfaces them as analyzer diagnostics (greenroom_broken) at the failure line. Built and verified; useful for humans hand-editing. Agents should just runflutter test.
Known limits #
- Parse-only discovery: direct
StatelessWidget/StatefulWidgetbases only for bare mounts/obligations;MultiPreviewsubclass expansion andtheme:/localizations:are not applied (noted, not silent); non-literal size/scale/brightness values are ignored with a note. - Screenshots render with REAL fonts (Roboto + MaterialIcons, loaded in
setUpAllfrom the running SDK's cache — graceful skip if absent) at the annotation's size or a default 800×600 surface. - Mount tests prove the FIRST FRAME with fixture data; interaction-time
behavior needs ordinary widget tests, which ride the same
flutter test.
Requires the Flutter SDK on PATH (developed against 3.47.0).