greenroom 0.2.0 copy "greenroom: ^0.2.0" to clipboard
greenroom: ^0.2.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 description
  • size: 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 = unconstrained
  • textScaleFactor: → MediaQuery text scaling
  • brightness: → MediaQuery platform brightness
  • wrapper: → applied around the entry, resolvable from ANY library
  • Preview/MultiPreview SUBCLASSES → expanded at runtime (const Sub().transform()), one verified mount per produced preview

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 #

Flutter's COMPLETE native error report — its stack, widget creation locations, its own remediation paragraphs, nothing summarized away — plus at most one added line, only where it encodes convention knowledge Flutter cannot have:

The following UnimplementedError was thrown building ProfileHeader:
UnimplementedError: LiveUserService not wired — missing fake?
#0  LiveUserService.displayName (package:example_app/services/user_service.dart:13:7)
#1  ProfileHeader.build (package:example_app/ui/profile_header.dart:20:39)
...

greenroom: Register a fixture-returning fake in lib/preview/world.dart.

Overflow IS a mount failure (A RenderFlex overflowed… fails the test, with Flutter's own advice intact). verdicts.json additionally carries the short structured form per entry: first error line, cascade count, the attributed file:line:col (first project stack frame, or the widget's creation location for layout errors with framework-only stacks), and the hint.

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 #

Discovery is fully RESOLVED (the target must be pub get-able): an annotation counts iff its class is declared in Flutter's widget_previews library — import spelling is irrelevant and name-squatting is impossible. The same annotations remain fully consumable by Flutter's interactive flutter widget-preview start; greenroom is its missing verification half.

  • wrapper: may live in ANY library — the tear-off's declaring library is imported by the generated test.
  • Preview/MultiPreview SUBCLASSES are expanded AT RUNTIME (const Sub().transform()), the official previewer's own architecture — closures and computed previews included.
  • Annotation fields resolve through constants (const kPhoneSize works, not just literals).
  • Widget detection is transitive (class Foo extends MyBaseCard is seen); InheritedWidgets are treated as supplies, never preview subjects.

Supported versions (all verified end-to-end) #

Dart SDK ^3.9.0 (Flutter 3.35, where @Preview shipped) — tested on 3.10.4 (Flutter 3.38.5) and 3.13.0 (Flutter 3.47.0)
analyzer >=12.1.0 <15.0.0 — tested at 12.1.0, 13.3.0, 14.0.0, 14.1.0

Known limits #

  • theme: / localizations: on direct @Preview are not applied (noted, not silent); subclass annotations get them for free via the runtime path.
  • Subclass annotations whose ARGUMENTS reference project code mount without fields (only dart:/flutter identifiers are re-emitted); argument-free subclasses — the normal shape — are fully supported.
  • Mount tests prove the FIRST FRAME with fixture data; interaction-time behavior needs ordinary widget tests, which ride the same flutter test.
  • Screenshots render with real fonts at the annotation's size or a default 800×600 surface.

Requires the Flutter SDK on PATH (developed against 3.47.0).

1
likes
0
points
169
downloads

Publisher

verified publisherwenkaifan.com

Weekly Downloads

Turns Flutter's own @Preview annotations into widget tests proving every preview mounts — with screenshots and a machine-readable verdicts file.

License

unknown (license)

Dependencies

analyzer, crypto, dart_style, path

More

Packages that depend on greenroom