The Widget Had the Right Width Factor and Rendered at 702 x 0

The progress fill had widthFactor: 1.0. The widget test found it and confirmed the property. The full button remained tappable.
The fill was also invisible.
Flutter had laid it out at Size(702, 0).
Technically, it occupied the full available width. It just did so with the vertical presence of a very committed line of philosophy.
The test asserted configuration, not output
The widget used a FractionallySizedBox inside an Align. Its width represented progress, so the test inspected the widget and checked widthFactor.
That assertion passed:
final fill = tester.widget<FractionallySizedBox>(
find.byKey(const Key('progress-fill')),
);
expect(fill.widthFactor, 1.0);
The FractionallySizedBox had no heightFactor. Under the surrounding constraints, its rendered height collapsed to zero.
A separate full-size InkWell still handled taps. Interaction tests passed because the hit target and the visual fill were different render objects.
The suite had verified two true statements:
- the progress value reached the widget;
- the button accepted input.
Neither statement proved that the fill occupied visible pixels.
Ask the render tree for geometry
The missing assertion was not another constructor property. It was the laid-out size:
final size = tester.getSize(
find.byKey(const Key('progress-fill')),
);
expect(size.width, greaterThan(0));
expect(
size.height,
greaterThan(0),
reason: 'Rendered fill size: $size',
);
For this regression, the important signal was height > 0. An assertion for the expected width could also protect the progress contract at known values.
WidgetTester.getSize reads the geometry produced after Flutter applies parent constraints, alignment, and sizing behavior. That is the layer where this bug existed.
The fix was correspondingly small:
FractionallySizedBox(
widthFactor: progress,
heightFactor: 1,
child: const ColoredBox(color: progressColor),
)
Adding heightFactor: 1 made the fill occupy the available height instead of deriving a zero extent from its child and constraints.
Property assertions still have a job
The lesson is not “never inspect widgets.” Property-level assertions are fast and useful when configuration is the contract.
If a callback should be present, a label should be selected, or a duration should be passed to an animation, inspecting the widget may be the most direct test.
The problem appears when the user-facing contract lives after layout:
- a fill must be visible;
- a hit target must meet a minimum size;
- a badge must stay inside bounds;
- a clipped element must retain meaningful area;
- aligned content must appear at the expected position.
For those cases, configuration is an input. Geometry is the output.
The regression needed one specific reader
I have a broader draft called “A Green Gate Only Proves What Its Reader Can See.” This regression supplies one concrete Flutter case for that principle.
The property reader saw widthFactor: 1.0. The interaction reader saw the full-size InkWell. Only a geometry query against the keyed fill could see the zero-height defect.
A golden test could have caught the missing pixels too, but this project had no golden setup. The smaller regression assertion asks for the fill's actual size and includes that Size in its failure reason. That keeps the test attached to the render object that failed instead of the tappable layer above it.
A green widget tree can still draw nothing
Flutter makes it easy to inspect declarative configuration. That convenience can pull tests toward what the code says it wants rather than what the render tree produced.
When a widget represents visible progress, ask one blunt question after layout: how many pixels did it receive?
If the answer is 702 x 0, the width factor is not reassuring. It is an extremely accurate description of nothing.
Get the next post.
If you made it to the end, meet the next post in your inbox or RSS reader.