fix: OutlineInputBorder not respecting BorderSide stroke alignment#180487
fix: OutlineInputBorder not respecting BorderSide stroke alignment#180487ikramhasan wants to merge 6 commits intoflutter:masterfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request modifies OutlineInputBorder to respect the strokeAlign property of BorderSide. The changes in packages/flutter/lib/src/material/input_border.dart use strokeInset and strokeOffset to adjust the border's dimensions and path based on the alignment. New tests have been added in packages/flutter/test/material/input_decorator_test.dart to verify the behavior for different strokeAlign values. My review includes a suggestion to refactor the new tests to reduce code duplication and improve maintainability, and a minor suggestion to improve test clarity.
| testWidgets( | ||
| 'OutlineInputBorder with BorderSide.strokeAlignOutside should draw border outside bounds', | ||
| (WidgetTester tester) async { | ||
| const borderWidth = 4.0; | ||
| const borderRadius = 12.0; | ||
| const inputDecoratorWidth = 800.0; | ||
| const inputDecoratorHeight = 56.0; | ||
|
|
||
| await tester.pumpWidget( | ||
| buildInputDecorator( | ||
| decoration: const InputDecoration( | ||
| filled: true, | ||
| fillColor: Color(0xFF00FF00), | ||
| enabledBorder: OutlineInputBorder( | ||
| borderRadius: BorderRadius.all(Radius.circular(borderRadius)), | ||
| borderSide: BorderSide( | ||
| width: borderWidth, | ||
| strokeAlign: BorderSide.strokeAlignOutside, | ||
| ), | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| final RenderBox box = tester.renderObject(find.byType(InputDecorator)); | ||
|
|
||
| expect( | ||
| box, | ||
| paints..rrect( | ||
| style: PaintingStyle.stroke, | ||
| strokeWidth: borderWidth, | ||
| rrect: RRect.fromLTRBR( | ||
| -borderWidth / 2, | ||
| -borderWidth / 2, | ||
| inputDecoratorWidth + borderWidth / 2, | ||
| inputDecoratorHeight + borderWidth / 2, | ||
| const Radius.circular(borderRadius + borderWidth / 2), | ||
| ), | ||
| ), | ||
| ); | ||
| }, | ||
| ); | ||
|
|
||
| testWidgets( | ||
| 'OutlineInputBorder with BorderSide.strokeAlignCenter should draw border between bounds', | ||
| (WidgetTester tester) async { | ||
| const borderWidth = 4.0; | ||
| const borderRadius = 12.0; | ||
| const inputDecoratorWidth = 800.0; | ||
| const inputDecoratorHeight = 56.0; | ||
|
|
||
| await tester.pumpWidget( | ||
| buildInputDecorator( | ||
| decoration: const InputDecoration( | ||
| filled: true, | ||
| fillColor: Color(0xFF00FF00), | ||
| enabledBorder: OutlineInputBorder( | ||
| borderRadius: BorderRadius.all(Radius.circular(borderRadius)), | ||
| borderSide: BorderSide( | ||
| width: borderWidth, | ||
| strokeAlign: BorderSide.strokeAlignCenter, | ||
| ), | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| final RenderBox box = tester.renderObject(find.byType(InputDecorator)); | ||
|
|
||
| expect( | ||
| box, | ||
| paints..rrect( | ||
| style: PaintingStyle.stroke, | ||
| strokeWidth: borderWidth, | ||
| rrect: RRect.fromLTRBR( | ||
| 0, | ||
| 0, | ||
| inputDecoratorWidth, | ||
| inputDecoratorHeight, | ||
| const Radius.circular(borderRadius), | ||
| ), | ||
| ), | ||
| ); | ||
| }, | ||
| ); | ||
|
|
||
| testWidgets( | ||
| 'OutlineInputBorder with BorderSide.strokeAlignInside should draw border inside bounds', | ||
| (WidgetTester tester) async { | ||
| const borderWidth = 4.0; | ||
| const borderRadius = 12.0; | ||
| const inputDecoratorWidth = 800.0; | ||
| const inputDecoratorHeight = 56.0; | ||
|
|
||
| await tester.pumpWidget( | ||
| buildInputDecorator( | ||
| decoration: const InputDecoration( | ||
| filled: true, | ||
| fillColor: Color(0xFF00FF00), | ||
| enabledBorder: OutlineInputBorder( | ||
| borderRadius: BorderRadius.all(Radius.circular(borderRadius)), | ||
| borderSide: BorderSide(width: borderWidth), | ||
| ), | ||
| ), | ||
| ), | ||
| ); | ||
|
|
||
| final RenderBox box = tester.renderObject(find.byType(InputDecorator)); | ||
|
|
||
| expect( | ||
| box, | ||
| paints..rrect( | ||
| style: PaintingStyle.stroke, | ||
| strokeWidth: borderWidth, | ||
| rrect: RRect.fromLTRBR( | ||
| borderWidth / 2, | ||
| borderWidth / 2, | ||
| inputDecoratorWidth - borderWidth / 2, | ||
| inputDecoratorHeight - borderWidth / 2, | ||
| const Radius.circular(borderRadius - borderWidth / 2), | ||
| ), | ||
| ), | ||
| ); | ||
| }, | ||
| ); |
There was a problem hiding this comment.
These three tests for strokeAlign are very similar and contain duplicated code. To improve maintainability and readability, consider refactoring them. One way to do this is to create a group for these tests and use a shared helper function to reduce boilerplate code.
For example, you could create a helper function like this:
Future<void> _testStrokeAlign({
required WidgetTester tester,
required double strokeAlign,
required RRect expectedRRect,
}) async {
const borderWidth = 4.0;
const borderRadius = 12.0;
const inputDecoratorWidth = 800.0;
const inputDecoratorHeight = 56.0;
await tester.pumpWidget(
buildInputDecorator(
decoration: InputDecoration(
filled: true,
fillColor: const Color(0xFF00FF00),
enabledBorder: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(borderRadius)),
borderSide: BorderSide(
width: borderWidth,
strokeAlign: strokeAlign,
),
),
),
),
);
final RenderBox box = tester.renderObject(find.byType(InputDecorator));
expect(
box,
paints..rrect(
style: PaintingStyle.stroke,
strokeWidth: borderWidth,
rrect: expectedRRect,
),
);
}Then, each test would become a simple call to this helper with the specific parameters.
References
- The style guide emphasizes optimizing for readability (line 29). While line 30 refers to state, the principle of avoiding duplication is also applicable to test code. Refactoring these tests would align with these principles by making the test suite cleaner and easier to maintain. (link)
| fillColor: Color(0xFF00FF00), | ||
| enabledBorder: OutlineInputBorder( | ||
| borderRadius: BorderRadius.all(Radius.circular(borderRadius)), | ||
| borderSide: BorderSide(width: borderWidth), |
There was a problem hiding this comment.
For clarity and to make the test more robust against future changes to default values, it's better to explicitly set strokeAlign: BorderSide.strokeAlignInside here, even though it's the default. This makes the test's intent clearer.
borderSide: BorderSide(width: borderWidth, strokeAlign: BorderSide.strokeAlignInside),…a single function for clarity
QuncCccccc
left a comment
There was a problem hiding this comment.
Thanks for the contribution! I think the change makes sense to me but it seems in the PR description, it shows me the label (the "inside", "center" and "outside") is no longer vertically centered and "outside" label is a bit lower than the border line. We might don't want it to change.
I actually made a comment regarding this in the issue this PR references. But to note again, the label alignment has not changed. It looks this way because of the decoration moving up or down. I'm not sure if the label is supposed to align with the textfield, or the decoration. Need confirmation on this. |
QuncCccccc
left a comment
There was a problem hiding this comment.
I'm not sure if the label is supposed to align with the textfield, or the decoration. Need confirmation on this.
Yes, the label should align with the border and keep vertically centered.
|
Hey @QuncCccccc, sorry for the late update. I updated the code to make sure the label aligns with the decoration now.
|
QuncCccccc
left a comment
There was a problem hiding this comment.
Nice fix:) LGTM. Thank you!
|
autosubmit label was removed for flutter/flutter/180487, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR. |


This PR fixes
OutlineInputBorderto properly respect the strokeAlign property ofBorderSide. Previously, the border was always drawn as if strokeAlign was set to strokeAlignInside, regardless of the actual value specified.Fixes: #179850
Before:

After:
Pre-launch Checklist
///).