Commit 74ebdaca authored by jimshepherd's avatar jimshepherd Committed by Hans Muller

Fix DropDownButton with no items resulting in RenderFlex overflow (#26143)

parent d5fd57cf
...@@ -557,7 +557,7 @@ class DropdownButton<T> extends StatefulWidget { ...@@ -557,7 +557,7 @@ class DropdownButton<T> extends StatefulWidget {
this.iconSize = 24.0, this.iconSize = 24.0,
this.isDense = false, this.isDense = false,
this.isExpanded = false, this.isExpanded = false,
}) : assert(items == null || value == null || items.where((DropdownMenuItem<T> item) => item.value == value).length == 1), }) : assert(items == null || items.isEmpty || value == null || items.where((DropdownMenuItem<T> item) => item.value == value).length == 1),
assert(elevation != null), assert(elevation != null),
assert(iconSize != null), assert(iconSize != null),
assert(isDense != null), assert(isDense != null),
...@@ -770,11 +770,17 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi ...@@ -770,11 +770,17 @@ class _DropdownButtonState<T> extends State<DropdownButton<T>> with WidgetsBindi
// If value is null (then _selectedIndex is null) or if disabled then we // If value is null (then _selectedIndex is null) or if disabled then we
// display the hint or nothing at all. // display the hint or nothing at all.
final IndexedStack innerItemsWidget = IndexedStack( final int index = _enabled ? (_selectedIndex ?? hintIndex) : hintIndex;
index: _enabled ? (_selectedIndex ?? hintIndex) : hintIndex, Widget innerItemsWidget;
alignment: AlignmentDirectional.centerStart, if (items.isEmpty) {
children: items, innerItemsWidget = Container();
); } else {
innerItemsWidget = IndexedStack(
index: index,
alignment: AlignmentDirectional.centerStart,
children: items,
);
}
Widget result = DefaultTextStyle( Widget result = DefaultTextStyle(
style: _textStyle, style: _textStyle,
......
...@@ -374,7 +374,7 @@ void main() { ...@@ -374,7 +374,7 @@ void main() {
Widget build() => buildFrame(buttonKey: buttonKey, value: value, textDirection: textDirection, onChanged: onChanged); Widget build() => buildFrame(buttonKey: buttonKey, value: value, textDirection: textDirection, onChanged: onChanged);
await tester.pumpWidget(build()); await tester.pumpWidget(build());
final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey)); final RenderBox buttonBox = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBox.attached); assert(buttonBox.attached);
final Offset buttonOriginBeforeTap = buttonBox.localToGlobal(Offset.zero); final Offset buttonOriginBeforeTap = buttonBox.localToGlobal(Offset.zero);
...@@ -388,7 +388,7 @@ void main() { ...@@ -388,7 +388,7 @@ void main() {
// The selected dropdown item is both in menu we just popped up, and in // The selected dropdown item is both in menu we just popped up, and in
// the IndexedStack contained by the dropdown button. Both of them should // the IndexedStack contained by the dropdown button. Both of them should
// have the same origin and height as the dropdown button. // have the same origin and height as the dropdown button.
final List<RenderObject> itemBoxes = tester.renderObjectList(find.byKey(const ValueKey<String>('two'))).toList(); final List<RenderObject> itemBoxes = tester.renderObjectList<RenderBox>(find.byKey(const ValueKey<String>('two'))).toList();
expect(itemBoxes.length, equals(2)); expect(itemBoxes.length, equals(2));
for (RenderBox itemBox in itemBoxes) { for (RenderBox itemBox in itemBoxes) {
assert(itemBox.attached); assert(itemBox.attached);
...@@ -419,10 +419,10 @@ void main() { ...@@ -419,10 +419,10 @@ void main() {
Widget build() => buildFrame(buttonKey: buttonKey, value: 'two', isExpanded: true, onChanged: onChanged); Widget build() => buildFrame(buttonKey: buttonKey, value: 'two', isExpanded: true, onChanged: onChanged);
await tester.pumpWidget(build()); await tester.pumpWidget(build());
final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey)); final RenderBox buttonBox = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBox.attached); assert(buttonBox.attached);
final RenderBox arrowIcon = tester.renderObject(find.byIcon(Icons.arrow_drop_down)); final RenderBox arrowIcon = tester.renderObject<RenderBox>(find.byIcon(Icons.arrow_drop_down));
assert(arrowIcon.attached); assert(arrowIcon.attached);
// Arrow icon should be aligned with far right of button when expanded // Arrow icon should be aligned with far right of button when expanded
...@@ -437,7 +437,7 @@ void main() { ...@@ -437,7 +437,7 @@ void main() {
Widget build() => buildFrame(buttonKey: buttonKey, value: value, isDense: true, onChanged: onChanged); Widget build() => buildFrame(buttonKey: buttonKey, value: value, isDense: true, onChanged: onChanged);
await tester.pumpWidget(build()); await tester.pumpWidget(build());
final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey)); final RenderBox buttonBox = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBox.attached); assert(buttonBox.attached);
await tester.tap(find.text('two')); await tester.tap(find.text('two'));
...@@ -504,7 +504,7 @@ void main() { ...@@ -504,7 +504,7 @@ void main() {
items: List<String>.generate(/*length=*/ 100, (int index) => index.toString()), items: List<String>.generate(/*length=*/ 100, (int index) => index.toString()),
onChanged: onChanged, onChanged: onChanged,
)); ));
final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey)); final RenderBox buttonBox = tester.renderObject<RenderBox>(find.byKey(buttonKey));
await tester.tap(find.byKey(buttonKey)); await tester.tap(find.byKey(buttonKey));
await tester.pumpAndSettle(); // finish the menu animation await tester.pumpAndSettle(); // finish the menu animation
...@@ -519,7 +519,6 @@ void main() { ...@@ -519,7 +519,6 @@ void main() {
); );
}); });
testWidgets('Size of DropdownButton with null value', (WidgetTester tester) async { testWidgets('Size of DropdownButton with null value', (WidgetTester tester) async {
final Key buttonKey = UniqueKey(); final Key buttonKey = UniqueKey();
String value; String value;
...@@ -527,13 +526,12 @@ void main() { ...@@ -527,13 +526,12 @@ void main() {
Widget build() => buildFrame(buttonKey: buttonKey, value: value, onChanged: onChanged); Widget build() => buildFrame(buttonKey: buttonKey, value: value, onChanged: onChanged);
await tester.pumpWidget(build()); await tester.pumpWidget(build());
final RenderBox buttonBoxNullValue = tester.renderObject(find.byKey(buttonKey)); final RenderBox buttonBoxNullValue = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBoxNullValue.attached); assert(buttonBoxNullValue.attached);
value = 'three'; value = 'three';
await tester.pumpWidget(build()); await tester.pumpWidget(build());
final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey)); final RenderBox buttonBox = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBox.attached); assert(buttonBox.attached);
// A Dropdown button with a null value should be the same size as a // A Dropdown button with a null value should be the same size as a
...@@ -542,6 +540,33 @@ void main() { ...@@ -542,6 +540,33 @@ void main() {
expect(buttonBox.size, equals(buttonBoxNullValue.size)); expect(buttonBox.size, equals(buttonBoxNullValue.size));
}); });
testWidgets('Size of DropdownButton with no items', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/26419
final Key buttonKey = UniqueKey();
List<String> items;
Widget build() => buildFrame(buttonKey: buttonKey, items: items, onChanged: onChanged);
await tester.pumpWidget(build());
final RenderBox buttonBoxNullItems = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBoxNullItems.attached);
items = <String>[];
await tester.pumpWidget(build());
final RenderBox buttonBoxEmptyItems = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBoxEmptyItems.attached);
items = <String>['one', 'two', 'three', 'four'];
await tester.pumpWidget(build());
final RenderBox buttonBox = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBox.attached);
// A Dropdown button with a null value should be the same size as a
// one with a non-null value.
expect(buttonBox.localToGlobal(Offset.zero), equals(buttonBoxNullItems.localToGlobal(Offset.zero)));
expect(buttonBox.size, equals(buttonBoxNullItems.size));
});
testWidgets('Layout of a DropdownButton with null value', (WidgetTester tester) async { testWidgets('Layout of a DropdownButton with null value', (WidgetTester tester) async {
final Key buttonKey = UniqueKey(); final Key buttonKey = UniqueKey();
String value; String value;
...@@ -553,7 +578,7 @@ void main() { ...@@ -553,7 +578,7 @@ void main() {
Widget build() => buildFrame(buttonKey: buttonKey, value: value, onChanged: onChanged); Widget build() => buildFrame(buttonKey: buttonKey, value: value, onChanged: onChanged);
await tester.pumpWidget(build()); await tester.pumpWidget(build());
final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey)); final RenderBox buttonBox = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBox.attached); assert(buttonBox.attached);
// Show the menu. // Show the menu.
...@@ -579,13 +604,12 @@ void main() { ...@@ -579,13 +604,12 @@ void main() {
await tester.pumpWidget(build()); await tester.pumpWidget(build());
expect(find.text('onetwothree'), findsOneWidget); expect(find.text('onetwothree'), findsOneWidget);
final RenderBox buttonBoxHintValue = tester.renderObject(find.byKey(buttonKey)); final RenderBox buttonBoxHintValue = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBoxHintValue.attached); assert(buttonBoxHintValue.attached);
value = 'three'; value = 'three';
await tester.pumpWidget(build()); await tester.pumpWidget(build());
final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey)); final RenderBox buttonBox = tester.renderObject<RenderBox>(find.byKey(buttonKey));
assert(buttonBox.attached); assert(buttonBox.attached);
// A Dropdown button with a null value and a hint should be the same size as a // A Dropdown button with a null value and a hint should be the same size as a
...@@ -714,7 +738,6 @@ void main() { ...@@ -714,7 +738,6 @@ void main() {
expect(find.byType(ListView, skipOffstage: false), findsNothing); expect(find.byType(ListView, skipOffstage: false), findsNothing);
}); });
testWidgets('Semantics Tree contains only selected element', (WidgetTester tester) async { testWidgets('Semantics Tree contains only selected element', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(buildFrame(items: menuItems, onChanged: onChanged)); await tester.pumpWidget(buildFrame(items: menuItems, onChanged: onChanged));
...@@ -853,14 +876,14 @@ void main() { ...@@ -853,14 +876,14 @@ void main() {
await tester.pumpWidget(build(items: menuItems, onChanged: null)); await tester.pumpWidget(build(items: menuItems, onChanged: null));
expect(find.text('enabled'), findsNothing); expect(find.text('enabled'), findsNothing);
expect(find.text('disabled'), findsOneWidget); expect(find.text('disabled'), findsOneWidget);
final RenderBox disabledHintBox = tester.renderObject(find.byKey(buttonKey)); final RenderBox disabledHintBox = tester.renderObject<RenderBox>(find.byKey(buttonKey));
// A Dropdown button with a disabled hint should be the same size as a // A Dropdown button with a disabled hint should be the same size as a
// one with a regular enabled hint. // one with a regular enabled hint.
await tester.pumpWidget(build(items: menuItems, onChanged: onChanged)); await tester.pumpWidget(build(items: menuItems, onChanged: onChanged));
expect(find.text('disabled'), findsNothing); expect(find.text('disabled'), findsNothing);
expect(find.text('enabled'), findsOneWidget); expect(find.text('enabled'), findsOneWidget);
final RenderBox enabledHintBox = tester.renderObject(find.byKey(buttonKey)); final RenderBox enabledHintBox = tester.renderObject<RenderBox>(find.byKey(buttonKey));
expect(enabledHintBox.localToGlobal(Offset.zero), equals(disabledHintBox.localToGlobal(Offset.zero))); expect(enabledHintBox.localToGlobal(Offset.zero), equals(disabledHintBox.localToGlobal(Offset.zero)));
expect(enabledHintBox.size, equals(disabledHintBox.size)); expect(enabledHintBox.size, equals(disabledHintBox.size));
}); });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment