1039 lines
37 KiB
Dart
1039 lines
37 KiB
Dart
// Copyright 2016 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'dart:ui' show window, SemanticsFlag;
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../widgets/semantics_tester.dart';
|
|
|
|
void main() {
|
|
testWidgets('Navigator.push works within a PopupMenuButton', (WidgetTester tester) async {
|
|
final Key targetKey = UniqueKey();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
routes: <String, WidgetBuilder>{
|
|
'/next': (BuildContext context) {
|
|
return const Text('Next');
|
|
},
|
|
},
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
key: targetKey,
|
|
builder: (BuildContext context) {
|
|
return PopupMenuButton<int>(
|
|
onSelected: (int value) {
|
|
Navigator.pushNamed(context, '/next');
|
|
},
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuItem<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('One'),
|
|
),
|
|
];
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byKey(targetKey));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1)); // finish the menu animation
|
|
|
|
expect(find.text('One'), findsOneWidget);
|
|
expect(find.text('Next'), findsNothing);
|
|
|
|
await tester.tap(find.text('One'));
|
|
await tester.pump(); // return the future
|
|
await tester.pump(); // start the navigation
|
|
await tester.pump(const Duration(seconds: 1)); // end the navigation
|
|
|
|
expect(find.text('One'), findsNothing);
|
|
expect(find.text('Next'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('PopupMenuButton calls onCanceled callback when an item is not selected', (WidgetTester tester) async {
|
|
int cancels = 0;
|
|
BuildContext popupContext;
|
|
final Key noCallbackKey = UniqueKey();
|
|
final Key withCallbackKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Column(
|
|
children: <Widget>[
|
|
PopupMenuButton<int>(
|
|
key: noCallbackKey,
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuEntry<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Tap me please!'),
|
|
),
|
|
];
|
|
},
|
|
),
|
|
PopupMenuButton<int>(
|
|
key: withCallbackKey,
|
|
onCanceled: () => cancels++,
|
|
itemBuilder: (BuildContext context) {
|
|
popupContext = context;
|
|
return <PopupMenuEntry<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Tap me, too!'),
|
|
),
|
|
];
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Make sure everything works if no callback is provided
|
|
await tester.tap(find.byKey(noCallbackKey));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
await tester.tapAt(const Offset(0.0, 0.0));
|
|
await tester.pump();
|
|
expect(cancels, equals(0));
|
|
|
|
// Make sure callback is called when a non-selection tap occurs
|
|
await tester.tap(find.byKey(withCallbackKey));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
await tester.tapAt(const Offset(0.0, 0.0));
|
|
await tester.pump();
|
|
expect(cancels, equals(1));
|
|
|
|
// Make sure callback is called when back navigation occurs
|
|
await tester.tap(find.byKey(withCallbackKey));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
Navigator.of(popupContext).pop();
|
|
await tester.pump();
|
|
expect(cancels, equals(2));
|
|
});
|
|
|
|
testWidgets('disabled PopupMenuButton will not call itemBuilder, onSelected or onCanceled', (WidgetTester tester) async {
|
|
final Key popupButtonKey = UniqueKey();
|
|
bool itemBuilderCalled = false;
|
|
bool onSelectedCalled = false;
|
|
bool onCanceledCalled = false;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Column(
|
|
children: <Widget>[
|
|
PopupMenuButton<int>(
|
|
key: popupButtonKey,
|
|
enabled: false,
|
|
itemBuilder: (BuildContext context) {
|
|
itemBuilderCalled = true;
|
|
return <PopupMenuEntry<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Tap me please!'),
|
|
),
|
|
];
|
|
},
|
|
onSelected: (int selected) => onSelectedCalled = true,
|
|
onCanceled: () => onCanceledCalled = true,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Try to bring up the popup menu and select the first item from it
|
|
await tester.tap(find.byKey(popupButtonKey));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byKey(popupButtonKey));
|
|
await tester.pumpAndSettle();
|
|
expect(itemBuilderCalled, isFalse);
|
|
expect(onSelectedCalled, isFalse);
|
|
|
|
// Try to bring up the popup menu and tap outside it to cancel the menu
|
|
await tester.tap(find.byKey(popupButtonKey));
|
|
await tester.pumpAndSettle();
|
|
await tester.tapAt(const Offset(0.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
expect(itemBuilderCalled, isFalse);
|
|
expect(onCanceledCalled, isFalse);
|
|
});
|
|
|
|
testWidgets('PopupMenuButton is horizontal on iOS', (WidgetTester tester) async {
|
|
Widget build(TargetPlatform platform) {
|
|
return MaterialApp(
|
|
theme: ThemeData(platform: platform),
|
|
home: Scaffold(
|
|
appBar: AppBar(
|
|
actions: <Widget>[
|
|
PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuItem<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('One'),
|
|
),
|
|
];
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(build(TargetPlatform.android));
|
|
|
|
expect(find.byIcon(Icons.more_vert), findsOneWidget);
|
|
expect(find.byIcon(Icons.more_horiz), findsNothing);
|
|
|
|
await tester.pumpWidget(build(TargetPlatform.iOS));
|
|
await tester.pumpAndSettle(); // Run theme change animation.
|
|
|
|
expect(find.byIcon(Icons.more_vert), findsNothing);
|
|
expect(find.byIcon(Icons.more_horiz), findsOneWidget);
|
|
});
|
|
|
|
group('PopupMenuButton with Icon', () {
|
|
// Helper function to create simple and valid popup menus.
|
|
List<PopupMenuItem<int>> simplePopupMenuItemBuilder(BuildContext context) {
|
|
return <PopupMenuItem<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('1'),
|
|
),
|
|
];
|
|
}
|
|
|
|
testWidgets('PopupMenuButton fails when given both child and icon', (WidgetTester tester) async {
|
|
expect(() {
|
|
PopupMenuButton<int>(
|
|
child: const Text('heyo'),
|
|
icon: const Icon(Icons.view_carousel),
|
|
itemBuilder: simplePopupMenuItemBuilder,
|
|
);
|
|
}, throwsA(isInstanceOf<AssertionError>()));
|
|
});
|
|
|
|
testWidgets('PopupMenuButton creates IconButton when given an icon', (WidgetTester tester) async {
|
|
final PopupMenuButton<int> button = PopupMenuButton<int>(
|
|
icon: const Icon(Icons.view_carousel),
|
|
itemBuilder: simplePopupMenuItemBuilder,
|
|
);
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Scaffold(
|
|
appBar: AppBar(
|
|
actions: <Widget>[button],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byType(IconButton), findsOneWidget);
|
|
expect(find.byIcon(Icons.view_carousel), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
testWidgets('PopupMenu positioning', (WidgetTester tester) async {
|
|
final Widget testButton = PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuItem<int>>[
|
|
const PopupMenuItem<int>(value: 1, child: Text('AAA')),
|
|
const PopupMenuItem<int>(value: 2, child: Text('BBB')),
|
|
const PopupMenuItem<int>(value: 3, child: Text('CCC')),
|
|
];
|
|
},
|
|
child: const SizedBox(
|
|
height: 100.0,
|
|
width: 100.0,
|
|
child: Text('XXX'),
|
|
),
|
|
);
|
|
final WidgetPredicate popupMenu = (Widget widget) {
|
|
final String widgetType = widget.runtimeType.toString();
|
|
// TODO(mraleph): Remove the old case below.
|
|
return widgetType == '_PopupMenu<int>' // normal case
|
|
|| widgetType == '_PopupMenu'; // for old versions of Dart that don't reify method type arguments
|
|
};
|
|
|
|
Future<void> openMenu(TextDirection textDirection, Alignment alignment) async {
|
|
return TestAsyncUtils.guard<void>(() async {
|
|
await tester.pumpWidget(Container()); // reset in case we had a menu up already
|
|
await tester.pumpWidget(TestApp(
|
|
textDirection: textDirection,
|
|
child: Align(
|
|
alignment: alignment,
|
|
child: testButton,
|
|
),
|
|
));
|
|
await tester.tap(find.text('XXX'));
|
|
await tester.pump();
|
|
});
|
|
}
|
|
|
|
Future<void> testPositioningDown(
|
|
WidgetTester tester,
|
|
TextDirection textDirection,
|
|
Alignment alignment,
|
|
TextDirection growthDirection,
|
|
Rect startRect,
|
|
) {
|
|
return TestAsyncUtils.guard<void>(() async {
|
|
await openMenu(textDirection, alignment);
|
|
Rect rect = tester.getRect(find.byWidgetPredicate(popupMenu));
|
|
expect(rect, startRect);
|
|
bool doneVertically = false;
|
|
bool doneHorizontally = false;
|
|
do {
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
final Rect newRect = tester.getRect(find.byWidgetPredicate(popupMenu));
|
|
expect(newRect.top, rect.top);
|
|
if (doneVertically) {
|
|
expect(newRect.bottom, rect.bottom);
|
|
} else {
|
|
if (newRect.bottom == rect.bottom) {
|
|
doneVertically = true;
|
|
} else {
|
|
expect(newRect.bottom, greaterThan(rect.bottom));
|
|
}
|
|
}
|
|
switch (growthDirection) {
|
|
case TextDirection.rtl:
|
|
expect(newRect.right, rect.right);
|
|
if (doneHorizontally) {
|
|
expect(newRect.left, rect.left);
|
|
} else {
|
|
if (newRect.left == rect.left) {
|
|
doneHorizontally = true;
|
|
} else {
|
|
expect(newRect.left, lessThan(rect.left));
|
|
}
|
|
}
|
|
break;
|
|
case TextDirection.ltr:
|
|
expect(newRect.left, rect.left);
|
|
if (doneHorizontally) {
|
|
expect(newRect.right, rect.right);
|
|
} else {
|
|
if (newRect.right == rect.right) {
|
|
doneHorizontally = true;
|
|
} else {
|
|
expect(newRect.right, greaterThan(rect.right));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
rect = newRect;
|
|
} while (tester.binding.hasScheduledFrame);
|
|
});
|
|
}
|
|
|
|
Future<void> testPositioningDownThenUp(
|
|
WidgetTester tester,
|
|
TextDirection textDirection,
|
|
Alignment alignment,
|
|
TextDirection growthDirection,
|
|
Rect startRect,
|
|
) {
|
|
return TestAsyncUtils.guard<void>(() async {
|
|
await openMenu(textDirection, alignment);
|
|
Rect rect = tester.getRect(find.byWidgetPredicate(popupMenu));
|
|
expect(rect, startRect);
|
|
int verticalStage = 0; // 0=down, 1=up, 2=done
|
|
bool doneHorizontally = false;
|
|
do {
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
final Rect newRect = tester.getRect(find.byWidgetPredicate(popupMenu));
|
|
switch (verticalStage) {
|
|
case 0:
|
|
if (newRect.top < rect.top) {
|
|
verticalStage = 1;
|
|
expect(newRect.bottom, greaterThanOrEqualTo(rect.bottom));
|
|
break;
|
|
}
|
|
expect(newRect.top, rect.top);
|
|
expect(newRect.bottom, greaterThan(rect.bottom));
|
|
break;
|
|
case 1:
|
|
if (newRect.top == rect.top) {
|
|
verticalStage = 2;
|
|
expect(newRect.bottom, rect.bottom);
|
|
break;
|
|
}
|
|
expect(newRect.top, lessThan(rect.top));
|
|
expect(newRect.bottom, rect.bottom);
|
|
break;
|
|
case 2:
|
|
expect(newRect.bottom, rect.bottom);
|
|
expect(newRect.top, rect.top);
|
|
break;
|
|
default:
|
|
assert(false);
|
|
}
|
|
switch (growthDirection) {
|
|
case TextDirection.rtl:
|
|
expect(newRect.right, rect.right);
|
|
if (doneHorizontally) {
|
|
expect(newRect.left, rect.left);
|
|
} else {
|
|
if (newRect.left == rect.left) {
|
|
doneHorizontally = true;
|
|
} else {
|
|
expect(newRect.left, lessThan(rect.left));
|
|
}
|
|
}
|
|
break;
|
|
case TextDirection.ltr:
|
|
expect(newRect.left, rect.left);
|
|
if (doneHorizontally) {
|
|
expect(newRect.right, rect.right);
|
|
} else {
|
|
if (newRect.right == rect.right) {
|
|
doneHorizontally = true;
|
|
} else {
|
|
expect(newRect.right, greaterThan(rect.right));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
rect = newRect;
|
|
} while (tester.binding.hasScheduledFrame);
|
|
});
|
|
}
|
|
|
|
await testPositioningDown(tester, TextDirection.ltr, Alignment.topRight, TextDirection.rtl, const Rect.fromLTWH(792.0, 8.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.rtl, Alignment.topRight, TextDirection.rtl, const Rect.fromLTWH(792.0, 8.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.ltr, Alignment.topLeft, TextDirection.ltr, const Rect.fromLTWH(8.0, 8.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.rtl, Alignment.topLeft, TextDirection.ltr, const Rect.fromLTWH(8.0, 8.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.ltr, Alignment.topCenter, TextDirection.ltr, const Rect.fromLTWH(350.0, 8.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.rtl, Alignment.topCenter, TextDirection.rtl, const Rect.fromLTWH(450.0, 8.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.ltr, Alignment.centerRight, TextDirection.rtl, const Rect.fromLTWH(792.0, 250.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.rtl, Alignment.centerRight, TextDirection.rtl, const Rect.fromLTWH(792.0, 250.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.ltr, Alignment.centerLeft, TextDirection.ltr, const Rect.fromLTWH(8.0, 250.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.rtl, Alignment.centerLeft, TextDirection.ltr, const Rect.fromLTWH(8.0, 250.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.ltr, Alignment.center, TextDirection.ltr, const Rect.fromLTWH(350.0, 250.0, 0.0, 0.0));
|
|
await testPositioningDown(tester, TextDirection.rtl, Alignment.center, TextDirection.rtl, const Rect.fromLTWH(450.0, 250.0, 0.0, 0.0));
|
|
await testPositioningDownThenUp(tester, TextDirection.ltr, Alignment.bottomRight, TextDirection.rtl, const Rect.fromLTWH(792.0, 500.0, 0.0, 0.0));
|
|
await testPositioningDownThenUp(tester, TextDirection.rtl, Alignment.bottomRight, TextDirection.rtl, const Rect.fromLTWH(792.0, 500.0, 0.0, 0.0));
|
|
await testPositioningDownThenUp(tester, TextDirection.ltr, Alignment.bottomLeft, TextDirection.ltr, const Rect.fromLTWH(8.0, 500.0, 0.0, 0.0));
|
|
await testPositioningDownThenUp(tester, TextDirection.rtl, Alignment.bottomLeft, TextDirection.ltr, const Rect.fromLTWH(8.0, 500.0, 0.0, 0.0));
|
|
await testPositioningDownThenUp(tester, TextDirection.ltr, Alignment.bottomCenter, TextDirection.ltr, const Rect.fromLTWH(350.0, 500.0, 0.0, 0.0));
|
|
await testPositioningDownThenUp(tester, TextDirection.rtl, Alignment.bottomCenter, TextDirection.rtl, const Rect.fromLTWH(450.0, 500.0, 0.0, 0.0));
|
|
});
|
|
|
|
testWidgets('PopupMenu removes MediaQuery padding', (WidgetTester tester) async {
|
|
BuildContext popupContext;
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: MediaQuery(
|
|
data: const MediaQueryData(
|
|
padding: EdgeInsets.all(50.0),
|
|
),
|
|
child: Material(
|
|
child: PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
popupContext = context;
|
|
return <PopupMenuItem<int>>[
|
|
PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
popupContext = context;
|
|
return const Text('AAA');
|
|
},
|
|
),
|
|
),
|
|
];
|
|
},
|
|
child: const SizedBox(
|
|
height: 100.0,
|
|
width: 100.0,
|
|
child: Text('XXX'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
));
|
|
|
|
await tester.tap(find.text('XXX'));
|
|
|
|
await tester.pump();
|
|
|
|
expect(MediaQuery.of(popupContext).padding, EdgeInsets.zero);
|
|
});
|
|
|
|
testWidgets('Popup Menu Offset Test', (WidgetTester tester) async {
|
|
const Offset offset = Offset(100.0, 100.0);
|
|
|
|
final PopupMenuButton<int> popupMenuButton =
|
|
PopupMenuButton<int>(
|
|
offset: offset,
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuItem<int>>[
|
|
PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return const Text('AAA');
|
|
},
|
|
),
|
|
),
|
|
];
|
|
},
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: Material(
|
|
child: popupMenuButton,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byType(IconButton));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The position is different than the offset because the default position isn't at the origin.
|
|
expect(tester.getTopLeft(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_PopupMenu<int>')), const Offset(364.0, 324.0));
|
|
});
|
|
|
|
testWidgets('open PopupMenu has correct semantics', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuItem<int>>[
|
|
const PopupMenuItem<int>(value: 1, child: Text('1')),
|
|
const PopupMenuItem<int>(value: 2, child: Text('2')),
|
|
const PopupMenuItem<int>(value: 3, child: Text('3')),
|
|
const PopupMenuItem<int>(value: 4, child: Text('4')),
|
|
const PopupMenuItem<int>(value: 5, child: Text('5')),
|
|
];
|
|
},
|
|
child: const SizedBox(
|
|
height: 100.0,
|
|
width: 100.0,
|
|
child: Text('XXX'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.tap(find.text('XXX'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(semantics, hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.scopesRoute,
|
|
SemanticsFlag.namesRoute,
|
|
],
|
|
label: 'Popup menu',
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasImplicitScrolling,
|
|
],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
label: '1',
|
|
textDirection: TextDirection.ltr,
|
|
),
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
label: '2',
|
|
textDirection: TextDirection.ltr,
|
|
),
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
label: '3',
|
|
textDirection: TextDirection.ltr,
|
|
),
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
label: '4',
|
|
textDirection: TextDirection.ltr,
|
|
),
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
label: '5',
|
|
textDirection: TextDirection.ltr,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreId: true, ignoreTransform: true, ignoreRect: true,
|
|
));
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('PopupMenuButton PopupMenuDivider', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/27072
|
|
|
|
String selectedValue;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Container(
|
|
child: Center(
|
|
child: PopupMenuButton<String>(
|
|
onSelected: (String result) {
|
|
selectedValue = result;
|
|
},
|
|
child: const Text('Menu Button'),
|
|
initialValue: '1',
|
|
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
|
const PopupMenuItem<String>(
|
|
child: Text('1'),
|
|
value: '1',
|
|
),
|
|
const PopupMenuDivider(),
|
|
const PopupMenuItem<String>(
|
|
child: Text('2'),
|
|
value: '2',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('Menu Button'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('1'), findsOneWidget);
|
|
expect(find.byType(PopupMenuDivider), findsOneWidget);
|
|
expect(find.text('2'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('1'));
|
|
await tester.pumpAndSettle();
|
|
expect(selectedValue, '1');
|
|
|
|
await tester.tap(find.text('Menu Button'));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('1'), findsOneWidget);
|
|
expect(find.byType(PopupMenuDivider), findsOneWidget);
|
|
expect(find.text('2'), findsOneWidget);
|
|
|
|
await tester.tap(find.text('2'));
|
|
await tester.pumpAndSettle();
|
|
expect(selectedValue, '2');
|
|
});
|
|
|
|
testWidgets('showMenu position required', (WidgetTester tester) async {
|
|
// Test for https://github.com/flutter/flutter/issues/22256
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return RaisedButton(
|
|
onPressed: () {
|
|
// Ensure showMenu throws an assertion without a position
|
|
expect(() {
|
|
// ignore: missing_required_param
|
|
showMenu<int>(
|
|
context: context,
|
|
items: <PopupMenuItem<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1, child: Text('1'),
|
|
),
|
|
],
|
|
);
|
|
}, throwsAssertionError);
|
|
},
|
|
child: const Text('Menu Button'),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('Menu Button'));
|
|
});
|
|
|
|
testWidgets('PopupMenuItem child height is a minimum, child is vertically centered', (WidgetTester tester) async {
|
|
final Key popupMenuButtonKey = UniqueKey();
|
|
final Type menuItemType = const PopupMenuItem<String>(child: Text('item')).runtimeType;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: PopupMenuButton<String>(
|
|
key: popupMenuButtonKey,
|
|
child: const Text('button'),
|
|
onSelected: (String result) { },
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuEntry<String>>[
|
|
// This menu item's height will be 48 because the default minimum height
|
|
// is 48 and the height of the text is less than 48.
|
|
const PopupMenuItem<String>(
|
|
value: '0',
|
|
child: Text('Item 0'),
|
|
),
|
|
// This menu item's height parameter specifies its minium height. The
|
|
// overall height of the menu item will be 50 because the child's
|
|
// height 40, is less than 50.
|
|
const PopupMenuItem<String>(
|
|
height: 50,
|
|
value: '1',
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: Text('Item 1'),
|
|
),
|
|
),
|
|
// This menu item's height parameter specifies its minium height, so the
|
|
// overall height of the menu item will be 75.
|
|
const PopupMenuItem<String>(
|
|
height: 75,
|
|
value: '2',
|
|
child: SizedBox(
|
|
child: Text('Item 2'),
|
|
),
|
|
),
|
|
// This menu item's height will be 100.
|
|
const PopupMenuItem<String>(
|
|
value: '3',
|
|
child: SizedBox(
|
|
height: 100,
|
|
child: Text('Item 3'),
|
|
),
|
|
),
|
|
];
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Show the menu
|
|
await tester.tap(find.byKey(popupMenuButtonKey));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The menu items and their InkWells should have the expected vertical size
|
|
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 0')).height, 48);
|
|
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 1')).height, 50);
|
|
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 2')).height, 75);
|
|
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 3')).height, 100);
|
|
expect(tester.getSize(find.widgetWithText(InkWell, 'Item 0')).height, 48);
|
|
expect(tester.getSize(find.widgetWithText(InkWell, 'Item 1')).height, 50);
|
|
expect(tester.getSize(find.widgetWithText(InkWell, 'Item 2')).height, 75);
|
|
expect(tester.getSize(find.widgetWithText(InkWell, 'Item 3')).height, 100);
|
|
|
|
// Menu item children which whose height is less than the PopupMenuItem
|
|
// are vertically centered.
|
|
expect(
|
|
tester.getRect(find.widgetWithText(menuItemType, 'Item 0')).center.dy,
|
|
tester.getRect(find.text('Item 0')).center.dy,
|
|
);
|
|
expect(
|
|
tester.getRect(find.widgetWithText(menuItemType, 'Item 2')).center.dy,
|
|
tester.getRect(find.text('Item 2')).center.dy,
|
|
);
|
|
});
|
|
|
|
testWidgets('Update PopupMenuItem layout while the menu is visible', (WidgetTester tester) async {
|
|
final Key popupMenuButtonKey = UniqueKey();
|
|
final Type menuItemType = const PopupMenuItem<String>(child: Text('item')).runtimeType;
|
|
|
|
Widget buildFrame({
|
|
TextDirection textDirection = TextDirection.ltr,
|
|
double fontSize = 24,
|
|
}) {
|
|
return MaterialApp(
|
|
builder: (BuildContext context, Widget child) {
|
|
return Directionality(
|
|
textDirection: textDirection,
|
|
child: PopupMenuTheme(
|
|
data: PopupMenuTheme.of(context).copyWith(
|
|
textStyle: Theme.of(context).textTheme.subhead.copyWith(fontSize: fontSize),
|
|
),
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
home: Scaffold(
|
|
body: PopupMenuButton<String>(
|
|
key: popupMenuButtonKey,
|
|
child: const Text('button'),
|
|
onSelected: (String result) { },
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuEntry<String>>[
|
|
const PopupMenuItem<String>(
|
|
value: '0',
|
|
child: Text('Item 0'),
|
|
),
|
|
const PopupMenuItem<String>(
|
|
value: '1',
|
|
child: Text('Item 1'),
|
|
),
|
|
];
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Show the menu
|
|
await tester.pumpWidget(buildFrame());
|
|
await tester.tap(find.byKey(popupMenuButtonKey));
|
|
await tester.pumpAndSettle();
|
|
|
|
// The menu items should have their default heights and horizontal alignment.
|
|
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 0')).height, 48);
|
|
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 1')).height, 48);
|
|
expect(tester.getTopLeft(find.text('Item 0')).dx, 24);
|
|
expect(tester.getTopLeft(find.text('Item 1')).dx, 24);
|
|
|
|
// While the menu is up, change its font size to 64 (default is 16).
|
|
await tester.pumpWidget(buildFrame(fontSize: 64));
|
|
await tester.pumpAndSettle(); // Theme changes are animated.
|
|
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 0')).height, 128);
|
|
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 1')).height, 128);
|
|
expect(tester.getSize(find.text('Item 0')).height, 128);
|
|
expect(tester.getSize(find.text('Item 1')).height, 128);
|
|
expect(tester.getTopLeft(find.text('Item 0')).dx, 24);
|
|
expect(tester.getTopLeft(find.text('Item 1')).dx, 24);
|
|
|
|
// While the menu is up, change the textDirection to rtl. Now menu items
|
|
// will be aligned right.
|
|
await tester.pumpWidget(buildFrame(textDirection: TextDirection.rtl));
|
|
await tester.pumpAndSettle(); // Theme changes are animated.
|
|
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 0')).height, 48);
|
|
expect(tester.getSize(find.widgetWithText(menuItemType, 'Item 1')).height, 48);
|
|
expect(tester.getTopLeft(find.text('Item 0')).dx, 72);
|
|
expect(tester.getTopLeft(find.text('Item 1')).dx, 72);
|
|
});
|
|
|
|
test("PopupMenuButton's child and icon properties cannot be simultaneously defined", () {
|
|
expect(() {
|
|
PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) => <PopupMenuItem<int>>[],
|
|
child: Container(),
|
|
icon: const Icon(Icons.error),
|
|
);
|
|
}, throwsAssertionError);
|
|
});
|
|
|
|
testWidgets('PopupMenuButton default tooltip', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Column(
|
|
children: <Widget>[
|
|
// Default Tooltip should be present when [PopupMenuButton.icon]
|
|
// and [PopupMenuButton.child] are undefined.
|
|
PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuEntry<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Tap me please!'),
|
|
),
|
|
];
|
|
},
|
|
),
|
|
// Default Tooltip should be present when
|
|
// [PopupMenuButton.child] is defined.
|
|
PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuEntry<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Tap me please!'),
|
|
),
|
|
];
|
|
},
|
|
child: const Text('Test text'),
|
|
),
|
|
// Default Tooltip should be present when
|
|
// [PopupMenuButton.icon] is defined.
|
|
PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuEntry<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Tap me please!'),
|
|
),
|
|
];
|
|
},
|
|
icon: const Icon(Icons.check),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// The default tooltip is defined as [MaterialLocalizations.showMenuTooltip]
|
|
// and it is used when no tooltip is provided.
|
|
expect(find.byType(Tooltip), findsNWidgets(3));
|
|
expect(find.byTooltip(const DefaultMaterialLocalizations().showMenuTooltip), findsNWidgets(3));
|
|
});
|
|
|
|
testWidgets('PopupMenuButton custom tooltip', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Column(
|
|
children: <Widget>[
|
|
// Tooltip should work when [PopupMenuButton.icon]
|
|
// and [PopupMenuButton.child] are undefined.
|
|
PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuEntry<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Tap me please!'),
|
|
),
|
|
];
|
|
},
|
|
tooltip: 'Test tooltip',
|
|
),
|
|
// Tooltip should work when
|
|
// [PopupMenuButton.child] is defined.
|
|
PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuEntry<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Tap me please!'),
|
|
),
|
|
];
|
|
},
|
|
tooltip: 'Test tooltip',
|
|
child: const Text('Test text'),
|
|
),
|
|
// Tooltip should work when
|
|
// [PopupMenuButton.icon] is defined.
|
|
PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuEntry<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Tap me please!'),
|
|
),
|
|
];
|
|
},
|
|
tooltip: 'Test tooltip',
|
|
icon: const Icon(Icons.check),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byType(Tooltip), findsNWidgets(3));
|
|
expect(find.byTooltip('Test tooltip',), findsNWidgets(3));
|
|
});
|
|
|
|
testWidgets('Allow Widget for PopupMenuButton.icon', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: PopupMenuButton<int>(
|
|
itemBuilder: (BuildContext context) {
|
|
return <PopupMenuEntry<int>>[
|
|
const PopupMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Tap me please!'),
|
|
),
|
|
];
|
|
},
|
|
tooltip: 'Test tooltip',
|
|
icon: const Text('PopupMenuButton icon'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('PopupMenuButton icon'), findsOneWidget);
|
|
});
|
|
}
|
|
|
|
class TestApp extends StatefulWidget {
|
|
const TestApp({ this.textDirection, this.child });
|
|
final TextDirection textDirection;
|
|
final Widget child;
|
|
@override
|
|
_TestAppState createState() => _TestAppState();
|
|
}
|
|
|
|
class _TestAppState extends State<TestApp> {
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Localizations(
|
|
locale: const Locale('en', 'US'),
|
|
delegates: const <LocalizationsDelegate<dynamic>>[
|
|
DefaultWidgetsLocalizations.delegate,
|
|
DefaultMaterialLocalizations.delegate,
|
|
],
|
|
child: MediaQuery(
|
|
data: MediaQueryData.fromWindow(window),
|
|
child: Directionality(
|
|
textDirection: widget.textDirection,
|
|
child: Navigator(
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
assert(settings.name == '/');
|
|
return MaterialPageRoute<void>(
|
|
settings: settings,
|
|
builder: (BuildContext context) => Material(
|
|
child: widget.child,
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|