From 7aa4d2d3ec2ad2d4d67ecce9ff11bf5506b97397 Mon Sep 17 00:00:00 2001 From: Nate Wilson Date: Fri, 13 Dec 2024 09:26:26 -0700 Subject: [PATCH] Fix `Stepper` connector not being properly displayed (#160193) @ayyoub-coder gave a fantastic summary of the problem in #160177: if a `Container` doesn't have a child or unbounded constraints, it will expand to fill its parent, whereas a `ColoredBox` defaults to zero size. I also noticed that in the main branch, the `PositionedDirectional` widget has an unused `width` parameter, and instead builds a `SizedBox` child for it. This pull request cleans up the `Stepper` subtree a bit and allows the connector to display properly. fixes #160156 --- .../flutter/lib/src/material/stepper.dart | 15 ++-- .../flutter/test/material/stepper_test.dart | 74 +++++++++++++++++++ 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/material/stepper.dart b/packages/flutter/lib/src/material/stepper.dart index 3a2978a483d..afe019227b0 100644 --- a/packages/flutter/lib/src/material/stepper.dart +++ b/packages/flutter/lib/src/material/stepper.dart @@ -784,21 +784,20 @@ class _StepperState extends State with TickerProviderStateMixin { start: 24.0 + (additionalMarginLeft ?? 0.0) + (additionalMarginRight ?? 0.0), top: 0.0, bottom: 0.0, - child: SizedBox( + width: _stepIconWidth ?? _kStepSize, + child: Center( // The line is drawn from the center of the circle vertically until // it reaches the bottom and then horizontally to the edge of the // stepper. - width: _stepIconWidth ?? _kStepSize, - child: Center( - child: SizedBox( - width: !_isLast(index) ? (widget.connectorThickness ?? 1.0) : 0.0, - child: ColoredBox(color: _connectorColor(widget.steps[index].isActive)), - ), + child: SizedBox( + width: !_isLast(index) ? (widget.connectorThickness ?? 1.0) : 0.0, + height: double.infinity, + child: ColoredBox(color: _connectorColor(widget.steps[index].isActive)), ), ), ), AnimatedCrossFade( - firstChild: const SizedBox(height: 0), + firstChild: const SizedBox(width: double.infinity, height: 0), secondChild: Padding( padding: EdgeInsetsDirectional.only( // Adjust [controlsBuilder] padding so that the content is diff --git a/packages/flutter/test/material/stepper_test.dart b/packages/flutter/test/material/stepper_test.dart index ee2052413a2..f34fa77c8d8 100644 --- a/packages/flutter/test/material/stepper_test.dart +++ b/packages/flutter/test/material/stepper_test.dart @@ -1815,6 +1815,80 @@ testWidgets('Stepper custom indexed controls test', (WidgetTester tester) async expect(getContentClipRect().clipBehavior, equals(Clip.hardEdge)); }); + + // Regression test for https://github.com/flutter/flutter/issues/160156. + testWidgets('Vertical stepper border displays correctly', (WidgetTester tester) async { + int index = 0; + const Color connectorColor = Color(0xff00ffff); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: StatefulBuilder( + builder: (BuildContext context, StateSetter setState) { + return Stepper( + currentStep: index, + connectorColor: const WidgetStatePropertyAll(connectorColor), + onStepTapped: (int value) { + setState(() { + index = value; + }); + }, + steps: const [ + Step( + title: Text('step1'), + content: Text('step1 content'), + ), + Step( + title: Text('step2'), + content: Text('step2 content'), + ), + ], + ); + } + ), + ), + ), + ), + ); + + final Finder findConnector = find.descendant( + of: find.byType(Stepper), + matching: find.descendant( + of: find.byType(PositionedDirectional), + matching: find.byElementPredicate((BuildContext context) { + if (context case BuildContext( + widget: ColoredBox(color: connectorColor), + size: Size(width: 1.0, height: > 0), + )) { + return true; + } + return false; + }), + ), + ); + + void verifyConnector() { + expect(findConnector, findsOneWidget); + final RenderBox renderBox = tester.renderObject(findConnector); + expect(renderBox, paints..rect(color: connectorColor)); + } + + verifyConnector(); + + final Finder findStep2 = find.text('step2'); + await tester.tap(findStep2); + + const int checkCount = 5; + final Duration duration = Duration( + microseconds: kThemeAnimationDuration.inMicroseconds ~/ (checkCount + 1), + ); + + for (int i = 0; i < checkCount; i++) { + await tester.pump(duration); + verifyConnector(); + } + }); } class _TappableColorWidget extends StatefulWidget {