Make UserAccountsDrawerHeader accessible (#13851)
Fixes #13743
Fixes #12379
Follow-up to #13745
Also adds an option to hide gestures introduced by `InkWell` and `InkResponse` from the semantics tree (see also `GestureDetector.excludeFromSemantics`).
diff --git a/examples/flutter_gallery/lib/demo/material/drawer_demo.dart b/examples/flutter_gallery/lib/demo/material/drawer_demo.dart
index 7eaa46c..8d96bd5 100644
--- a/examples/flutter_gallery/lib/demo/material/drawer_demo.dart
+++ b/examples/flutter_gallery/lib/demo/material/drawer_demo.dart
@@ -100,17 +100,33 @@
package: _kGalleryAssetsPackage,
),
),
- otherAccountsPictures: const <Widget>[
- const CircleAvatar(
- backgroundImage: const AssetImage(
- _kAsset1,
- package: _kGalleryAssetsPackage,
+ otherAccountsPictures: <Widget>[
+ new GestureDetector(
+ onTap: () {
+ _onOtherAccountsTap(context);
+ },
+ child: new Semantics(
+ label: 'Switch to Account B',
+ child: const CircleAvatar(
+ backgroundImage: const AssetImage(
+ _kAsset1,
+ package: _kGalleryAssetsPackage,
+ ),
+ ),
),
),
- const CircleAvatar(
- backgroundImage: const AssetImage(
- _kAsset2,
- package: _kGalleryAssetsPackage,
+ new GestureDetector(
+ onTap: () {
+ _onOtherAccountsTap(context);
+ },
+ child: new Semantics(
+ label: 'Switch to Account C',
+ child: const CircleAvatar(
+ backgroundImage: const AssetImage(
+ _kAsset2,
+ package: _kGalleryAssetsPackage,
+ ),
+ ),
),
),
],
@@ -213,4 +229,21 @@
),
);
}
+
+ void _onOtherAccountsTap(BuildContext context) {
+ showDialog<Null>(
+ context: context,
+ child: new AlertDialog(
+ title: const Text('Account switching not implemented.'),
+ actions: <Widget>[
+ new FlatButton(
+ child: const Text('OK'),
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ ),
+ ],
+ ),
+ );
+ }
}
diff --git a/packages/flutter/lib/src/material/ink_well.dart b/packages/flutter/lib/src/material/ink_well.dart
index 4707082..1186870 100644
--- a/packages/flutter/lib/src/material/ink_well.dart
+++ b/packages/flutter/lib/src/material/ink_well.dart
@@ -96,6 +96,7 @@
this.highlightColor,
this.splashColor,
this.enableFeedback: true,
+ this.excludeFromSemantics: false,
}) : assert(enableFeedback != null), super(key: key);
/// The widget below this widget in the tree.
@@ -194,6 +195,15 @@
/// * [Feedback] for providing platform-specific feedback to certain actions.
final bool enableFeedback;
+ /// Whether to exclude the gestures introduced by this widget from the
+ /// semantics tree.
+ ///
+ /// For example, a long-press gesture for showing a tooltip is usually
+ /// excluded because the tooltip itself is included in the semantics
+ /// tree directly and so having a gesture to show it would result in
+ /// duplication of information.
+ final bool excludeFromSemantics;
+
/// The rectangle to use for the highlight effect and for clipping
/// the splash effects if [containedInkWell] is true.
///
@@ -379,7 +389,8 @@
onDoubleTap: widget.onDoubleTap != null ? _handleDoubleTap : null,
onLongPress: widget.onLongPress != null ? () => _handleLongPress(context) : null,
behavior: HitTestBehavior.opaque,
- child: widget.child
+ child: widget.child,
+ excludeFromSemantics: widget.excludeFromSemantics,
);
}
@@ -427,6 +438,7 @@
Color splashColor,
BorderRadius borderRadius,
bool enableFeedback: true,
+ bool excludeFromSemantics: false,
}) : super(
key: key,
child: child,
@@ -440,5 +452,6 @@
splashColor: splashColor,
borderRadius: borderRadius,
enableFeedback: enableFeedback,
+ excludeFromSemantics: excludeFromSemantics,
);
}
diff --git a/packages/flutter/lib/src/material/user_accounts_drawer_header.dart b/packages/flutter/lib/src/material/user_accounts_drawer_header.dart
index d793a85..58bb168 100644
--- a/packages/flutter/lib/src/material/user_accounts_drawer_header.dart
+++ b/packages/flutter/lib/src/material/user_accounts_drawer_header.dart
@@ -10,6 +10,7 @@
import 'drawer_header.dart';
import 'icons.dart';
import 'ink_well.dart';
+import 'material_localizations.dart';
import 'theme.dart';
class _AccountPictures extends StatelessWidget {
@@ -31,21 +32,27 @@
end: 0.0,
child: new Row(
children: (otherAccountsPictures ?? <Widget>[]).take(3).map((Widget picture) {
- return new Container(
- margin: const EdgeInsetsDirectional.only(start: 16.0),
- width: 40.0,
- height: 40.0,
- child: picture
+ return new Semantics(
+ explicitChildNodes: true,
+ child: new Container(
+ margin: const EdgeInsetsDirectional.only(start: 16.0),
+ width: 40.0,
+ height: 40.0,
+ child: picture
+ ),
);
}).toList(),
),
),
new Positioned(
top: 0.0,
- child: new SizedBox(
- width: 72.0,
- height: 72.0,
- child: currentAccountPicture
+ child: new Semantics(
+ explicitChildNodes: true,
+ child: new SizedBox(
+ width: 72.0,
+ height: 72.0,
+ child: currentAccountPicture
+ ),
),
),
],
@@ -67,66 +74,170 @@
final VoidCallback onTap;
final bool isOpen;
- Widget addDropdownIcon(Widget line) {
- final Widget icon = new Icon(
- isOpen ? Icons.arrow_drop_up : Icons.arrow_drop_down,
- color: Colors.white
- );
- return new Expanded(
- child: new Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: line == null ? <Widget>[icon] : <Widget>[
- new Expanded(child: line),
- icon,
- ],
- ),
- );
- }
-
@override
Widget build(BuildContext context) {
+ assert(debugCheckHasDirectionality(context));
+
final ThemeData theme = Theme.of(context);
- Widget accountNameLine = accountName == null ? null : new DefaultTextStyle(
- style: theme.primaryTextTheme.body2,
- overflow: TextOverflow.ellipsis,
- child: accountName,
- );
- Widget accountEmailLine = accountEmail == null ? null : new DefaultTextStyle(
- style: theme.primaryTextTheme.body1,
- overflow: TextOverflow.ellipsis,
- child: accountEmail,
- );
- if (onTap != null) {
- if (accountEmailLine != null)
- accountEmailLine = addDropdownIcon(accountEmailLine);
- else
- accountNameLine = addDropdownIcon(accountNameLine);
+ final List<Widget> children = <Widget>[];
+
+ if (accountName != null) {
+ final Widget accountNameLine = new LayoutId(
+ id: _AccountDetailsLayout.accountName,
+ child: new Padding(
+ padding: const EdgeInsets.symmetric(vertical: 2.0),
+ child: new DefaultTextStyle(
+ style: theme.primaryTextTheme.body2,
+ overflow: TextOverflow.ellipsis,
+ child: accountName,
+ ),
+ ),
+ );
+ children.add(accountNameLine);
}
- Widget accountDetails;
- if (accountEmailLine != null || accountNameLine != null) {
- accountDetails = new Padding(
- padding: const EdgeInsets.symmetric(vertical: 8.0),
- child: new Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: (accountEmailLine != null && accountNameLine != null)
- ? <Widget>[accountNameLine, accountEmailLine]
- : <Widget>[accountNameLine ?? accountEmailLine]
+ if (accountEmail != null) {
+ final Widget accountEmailLine = new LayoutId(
+ id: _AccountDetailsLayout.accountEmail,
+ child: new Padding(
+ padding: const EdgeInsets.symmetric(vertical: 2.0),
+ child: new DefaultTextStyle(
+ style: theme.primaryTextTheme.body1,
+ overflow: TextOverflow.ellipsis,
+ child: accountEmail,
+ ),
),
);
+ children.add(accountEmailLine);
+ }
+
+ if (onTap != null) {
+ final MaterialLocalizations localizations = MaterialLocalizations.of(context);
+ final Widget dropDownIcon = new LayoutId(
+ id: _AccountDetailsLayout.dropdownIcon,
+ child: new Semantics(
+ container: true,
+ button: true,
+ onTap: onTap,
+ child: new SizedBox(
+ height: _kAccountDetailsHeight,
+ width: _kAccountDetailsHeight,
+ child: new Center(
+ child: new Icon(
+ isOpen ? Icons.arrow_drop_up : Icons.arrow_drop_down,
+ color: Colors.white,
+ semanticLabel: isOpen
+ ? localizations.hideAccountsLabel
+ : localizations.showAccountsLabel,
+ ),
+ ),
+ ),
+ ),
+ );
+ children.add(dropDownIcon);
+ }
+
+ Widget accountDetails = new CustomMultiChildLayout(
+ delegate: new _AccountDetailsLayout(
+ textDirection: Directionality.of(context),
+ ),
+ children: children,
+ );
+
+ if (onTap != null) {
+ accountDetails = new InkWell(
+ onTap: onTap,
+ child: accountDetails,
+ excludeFromSemantics: true,
+ );
}
- if (onTap != null)
- accountDetails = new InkWell(onTap: onTap, child: accountDetails);
-
return new SizedBox(
- height: 56.0,
+ height: _kAccountDetailsHeight,
child: accountDetails,
);
}
}
+const double _kAccountDetailsHeight = 56.0;
+
+class _AccountDetailsLayout extends MultiChildLayoutDelegate {
+
+ _AccountDetailsLayout({ @required this.textDirection });
+
+ static final String accountName = 'accountName';
+ static final String accountEmail = 'accountEmail';
+ static final String dropdownIcon = 'dropdownIcon';
+
+ final TextDirection textDirection;
+
+ @override
+ void performLayout(Size size) {
+ Size iconSize;
+ if (hasChild(dropdownIcon)) {
+ // place the dropdown icon in bottom right (LTR) or bottom left (RTL)
+ iconSize = layoutChild(dropdownIcon, new BoxConstraints.loose(size));
+ positionChild(dropdownIcon, _offsetForIcon(size, iconSize));
+ }
+
+ final String bottomLine = hasChild(accountEmail) ? accountEmail : (hasChild(accountName) ? accountName : null);
+
+ if (bottomLine != null) {
+ final Size constraintSize = iconSize == null ? size : size - new Offset(iconSize.width, 0.0);
+ iconSize ??= const Size(_kAccountDetailsHeight, _kAccountDetailsHeight);
+
+ // place bottom line center at same height as icon center
+ final Size bottomLineSize = layoutChild(bottomLine, new BoxConstraints.loose(constraintSize));
+ final Offset bottomLineOffset = _offsetForBottomLine(size, iconSize, bottomLineSize);
+ positionChild(bottomLine, bottomLineOffset);
+
+ // place account name above account email
+ if (bottomLine == accountEmail && hasChild(accountName)) {
+ final Size nameSize = layoutChild(accountName, new BoxConstraints.loose(constraintSize));
+ positionChild(accountName, _offsetForName(size, nameSize, bottomLineOffset));
+ }
+ }
+ }
+
+ @override
+ bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => true;
+
+ Offset _offsetForIcon(Size size, Size iconSize) {
+ switch (textDirection) {
+ case TextDirection.ltr:
+ return new Offset(size.width - iconSize.width, size.height - iconSize.height);
+ case TextDirection.rtl:
+ return new Offset(0.0, size.height - iconSize.height);
+ }
+ assert(false, 'Unreachable');
+ return null;
+ }
+
+ Offset _offsetForBottomLine(Size size, Size iconSize, Size bottomLineSize) {
+ final double y = size.height - 0.5 * iconSize.height - 0.5 * bottomLineSize.height;
+ switch (textDirection) {
+ case TextDirection.ltr:
+ return new Offset(0.0, y);
+ case TextDirection.rtl:
+ return new Offset(size.width - bottomLineSize.width, y);
+ }
+ assert(false, 'Unreachable');
+ return null;
+ }
+
+ Offset _offsetForName(Size size, Size nameSize, Offset bottomLineOffset) {
+ final double y = bottomLineOffset.dy - nameSize.height;
+ switch (textDirection) {
+ case TextDirection.ltr:
+ return new Offset(0.0, y);
+ case TextDirection.rtl:
+ return new Offset(size.width - nameSize.width, y);
+ }
+ assert(false, 'Unreachable');
+ return null;
+ }
+}
+
/// A material design [Drawer] header that identifies the app's user.
///
/// Requires one of its ancestors to be a [Material] widget.
@@ -195,29 +306,37 @@
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterial(context));
- return new DrawerHeader(
- decoration: widget.decoration ?? new BoxDecoration(
- color: Theme.of(context).primaryColor,
- ),
- margin: widget.margin,
- child: new SafeArea(
- bottom: false,
- child: new Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: <Widget>[
- new Expanded(
- child: new _AccountPictures(
- currentAccountPicture: widget.currentAccountPicture,
- otherAccountsPictures: widget.otherAccountsPictures,
- )
- ),
- new _AccountDetails(
- accountName: widget.accountName,
- accountEmail: widget.accountEmail,
- isOpen: _isOpen,
- onTap: widget.onDetailsPressed == null ? null : _handleDetailsPressed,
- ),
- ],
+ return new Semantics(
+ container: true,
+ label: MaterialLocalizations.of(context).signedInLabel,
+ child: new DrawerHeader(
+ decoration: widget.decoration ?? new BoxDecoration(
+ color: Theme.of(context).primaryColor,
+ ),
+ margin: widget.margin,
+ padding: const EdgeInsetsDirectional.only(top: 16.0, start: 16.0),
+ child: new SafeArea(
+ bottom: false,
+ child: new Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: <Widget>[
+ new Expanded(
+ child: new Padding(
+ padding: const EdgeInsetsDirectional.only(end: 16.0),
+ child: new _AccountPictures(
+ currentAccountPicture: widget.currentAccountPicture,
+ otherAccountsPictures: widget.otherAccountsPictures,
+ ),
+ )
+ ),
+ new _AccountDetails(
+ accountName: widget.accountName,
+ accountEmail: widget.accountEmail,
+ isOpen: _isOpen,
+ onTap: widget.onDetailsPressed == null ? null : _handleDetailsPressed,
+ ),
+ ],
+ ),
),
),
);
diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart
index f2d4c6a..5b0872b 100644
--- a/packages/flutter/test/material/ink_well_test.dart
+++ b/packages/flutter/test/material/ink_well_test.dart
@@ -7,6 +7,7 @@
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
+import '../widgets/semantics_tester.dart';
import 'feedback_tester.dart';
void main() {
@@ -156,4 +157,33 @@
await runTest(true);
await runTest(false);
});
+
+ testWidgets('excludeFromSemantics', (WidgetTester tester) async {
+ final SemanticsTester semantics = new SemanticsTester(tester);
+
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new Material(
+ child: new InkWell(
+ onTap: () { },
+ child: const Text('Button'),
+ ),
+ ),
+ ));
+ expect(semantics, includesNodeWith(label: 'Button', actions: <SemanticsAction>[SemanticsAction.tap]));
+
+ await tester.pumpWidget(new Directionality(
+ textDirection: TextDirection.ltr,
+ child: new Material(
+ child: new InkWell(
+ onTap: () { },
+ child: const Text('Button'),
+ excludeFromSemantics: true,
+ ),
+ ),
+ ));
+ expect(semantics, isNot(includesNodeWith(label: 'Button', actions: <SemanticsAction>[SemanticsAction.tap])));
+
+ semantics.dispose();
+ });
}
diff --git a/packages/flutter/test/material/user_accounts_drawer_header_test.dart b/packages/flutter/test/material/user_accounts_drawer_header_test.dart
index f18956e..c304c34 100644
--- a/packages/flutter/test/material/user_accounts_drawer_header_test.dart
+++ b/packages/flutter/test/material/user_accounts_drawer_header_test.dart
@@ -2,59 +2,74 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:ui' show SemanticsFlags;
+
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
-void main() {
- testWidgets('UserAccountsDrawerHeader test', (WidgetTester tester) async {
- final Key avatarA = const Key('A');
- final Key avatarC = const Key('C');
- final Key avatarD = const Key('D');
+import '../widgets/semantics_tester.dart';
- await tester.pumpWidget(
- new MaterialApp(
- home: new MediaQuery(
- data: const MediaQueryData(
- padding: const EdgeInsets.only(
- left: 10.0,
- top: 20.0,
- right: 30.0,
- bottom: 40.0,
- ),
+const Key avatarA = const Key('A');
+const Key avatarC = const Key('C');
+const Key avatarD = const Key('D');
+
+Future<Null> pumpTestWidget(WidgetTester tester, {
+ bool withName: true,
+ bool withEmail: true,
+ bool withOnDetailsPressedHandler: true,
+}) async {
+ await tester.pumpWidget(
+ new MaterialApp(
+ home: new MediaQuery(
+ data: const MediaQueryData(
+ padding: const EdgeInsets.only(
+ left: 10.0,
+ top: 20.0,
+ right: 30.0,
+ bottom: 40.0,
),
- child: new Material(
- child: new Center(
- child: new UserAccountsDrawerHeader(
- currentAccountPicture: new CircleAvatar(
+ ),
+ child: new Material(
+ child: new Center(
+ child: new UserAccountsDrawerHeader(
+ onDetailsPressed: withOnDetailsPressedHandler ? () {} : null,
+ currentAccountPicture: const ExcludeSemantics(
+ child: const CircleAvatar(
key: avatarA,
child: const Text('A'),
),
- otherAccountsPictures: <Widget>[
- const CircleAvatar(
- child: const Text('B'),
- ),
- new CircleAvatar(
- key: avatarC,
- child: const Text('C'),
- ),
- new CircleAvatar(
- key: avatarD,
- child: const Text('D'),
- ),
- const CircleAvatar(
- child: const Text('E'),
- )
- ],
- accountName: const Text('name'),
- accountEmail: const Text('email'),
),
+ otherAccountsPictures: <Widget>[
+ const CircleAvatar(
+ child: const Text('B'),
+ ),
+ const CircleAvatar(
+ key: avatarC,
+ child: const Text('C'),
+ ),
+ const CircleAvatar(
+ key: avatarD,
+ child: const Text('D'),
+ ),
+ const CircleAvatar(
+ child: const Text('E'),
+ )
+ ],
+ accountName: withName ? const Text('name') : null,
+ accountEmail: withEmail ? const Text('email') : null,
),
),
),
),
- );
+ ),
+ );
+}
+void main() {
+ testWidgets('UserAccountsDrawerHeader test', (WidgetTester tester) async {
+ await pumpTestWidget(tester);
+
expect(find.text('A'), findsOneWidget);
expect(find.text('B'), findsOneWidget);
expect(find.text('C'), findsOneWidget);
@@ -91,7 +106,7 @@
});
- testWidgets('UserAccountsDrawerHeader null parameters', (WidgetTester tester) async {
+ testWidgets('UserAccountsDrawerHeader null parameters LTR', (WidgetTester tester) async {
Widget buildFrame({
Widget currentAccountPicture,
List<Widget> otherAccountsPictures,
@@ -134,6 +149,10 @@
tester.getCenter(find.text('accountName')).dy,
tester.getCenter(find.byType(Icon)).dy
);
+ expect(
+ tester.getCenter(find.text('accountName')).dx,
+ lessThan(tester.getCenter(find.byType(Icon)).dx)
+ );
await tester.pumpWidget(buildFrame(
accountEmail: const Text('accountEmail'),
@@ -143,6 +162,10 @@
tester.getCenter(find.text('accountEmail')).dy,
tester.getCenter(find.byType(Icon)).dy
);
+ expect(
+ tester.getCenter(find.text('accountEmail')).dx,
+ lessThan(tester.getCenter(find.byType(Icon)).dx)
+ );
await tester.pumpWidget(buildFrame(
accountName: const Text('accountName'),
@@ -154,6 +177,10 @@
tester.getCenter(find.byType(Icon)).dy
);
expect(
+ tester.getCenter(find.text('accountEmail')).dx,
+ lessThan(tester.getCenter(find.byType(Icon)).dx)
+ );
+ expect(
tester.getBottomLeft(find.text('accountEmail')).dy,
greaterThan(tester.getBottomLeft(find.text('accountName')).dy)
);
@@ -186,4 +213,198 @@
greaterThan(tester.getBottomLeft(find.byKey(avatarA)).dy)
);
});
+
+ testWidgets('UserAccountsDrawerHeader null parameters RTL', (WidgetTester tester) async {
+ Widget buildFrame({
+ Widget currentAccountPicture,
+ List<Widget> otherAccountsPictures,
+ Widget accountName,
+ Widget accountEmail,
+ VoidCallback onDetailsPressed,
+ EdgeInsets margin,
+ }) {
+ return new MaterialApp(
+ home: new Directionality(
+ textDirection: TextDirection.rtl,
+ child: new Material(
+ child: new Center(
+ child: new UserAccountsDrawerHeader(
+ currentAccountPicture: currentAccountPicture,
+ otherAccountsPictures: otherAccountsPictures,
+ accountName: accountName,
+ accountEmail: accountEmail,
+ onDetailsPressed: onDetailsPressed,
+ margin: margin,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ await tester.pumpWidget(buildFrame());
+ final RenderBox box = tester.renderObject(find.byType(UserAccountsDrawerHeader));
+ expect(box.size.height, equals(160.0 + 1.0)); // height + bottom edge)
+ expect(find.byType(Icon), findsNothing);
+
+ await tester.pumpWidget(buildFrame(
+ onDetailsPressed: () { },
+ ));
+ expect(find.byType(Icon), findsOneWidget);
+
+ await tester.pumpWidget(buildFrame(
+ accountName: const Text('accountName'),
+ onDetailsPressed: () { },
+ ));
+ expect(
+ tester.getCenter(find.text('accountName')).dy,
+ tester.getCenter(find.byType(Icon)).dy
+ );
+ expect(
+ tester.getCenter(find.text('accountName')).dx,
+ greaterThan(tester.getCenter(find.byType(Icon)).dx)
+ );
+
+ await tester.pumpWidget(buildFrame(
+ accountEmail: const Text('accountEmail'),
+ onDetailsPressed: () { },
+ ));
+ expect(
+ tester.getCenter(find.text('accountEmail')).dy,
+ tester.getCenter(find.byType(Icon)).dy
+ );
+ expect(
+ tester.getCenter(find.text('accountEmail')).dx,
+ greaterThan(tester.getCenter(find.byType(Icon)).dx)
+ );
+
+ await tester.pumpWidget(buildFrame(
+ accountName: const Text('accountName'),
+ accountEmail: const Text('accountEmail'),
+ onDetailsPressed: () { },
+ ));
+ expect(
+ tester.getCenter(find.text('accountEmail')).dy,
+ tester.getCenter(find.byType(Icon)).dy
+ );
+ expect(
+ tester.getCenter(find.text('accountEmail')).dx,
+ greaterThan(tester.getCenter(find.byType(Icon)).dx)
+ );
+ expect(
+ tester.getBottomLeft(find.text('accountEmail')).dy,
+ greaterThan(tester.getBottomLeft(find.text('accountName')).dy)
+ );
+ expect(
+ tester.getBottomRight(find.text('accountEmail')).dx,
+ tester.getBottomRight(find.text('accountName')).dx
+ );
+
+ await tester.pumpWidget(buildFrame(
+ currentAccountPicture: const CircleAvatar(child: const Text('A')),
+ ));
+ expect(find.text('A'), findsOneWidget);
+
+ await tester.pumpWidget(buildFrame(
+ otherAccountsPictures: <Widget>[const CircleAvatar(child: const Text('A'))],
+ ));
+ expect(find.text('A'), findsOneWidget);
+
+ final Key avatarA = const Key('A');
+ await tester.pumpWidget(buildFrame(
+ currentAccountPicture: new CircleAvatar(key: avatarA, child: const Text('A')),
+ accountName: const Text('accountName'),
+ ));
+ expect(
+ tester.getBottomRight(find.byKey(avatarA)).dx,
+ tester.getBottomRight(find.text('accountName')).dx
+ );
+ expect(
+ tester.getBottomLeft(find.text('accountName')).dy,
+ greaterThan(tester.getBottomLeft(find.byKey(avatarA)).dy)
+ );
+ });
+
+ testWidgets('UserAccountsDrawerHeader provides semantics', (WidgetTester tester) async {
+ final SemanticsTester semantics = new SemanticsTester(tester);
+ await pumpTestWidget(tester);
+
+ expect(
+ semantics,
+ hasSemantics(
+ new TestSemantics(
+ children: <TestSemantics>[
+ new TestSemantics(
+ label: 'Signed in\nname\nemail',
+ textDirection: TextDirection.ltr,
+ children: <TestSemantics>[
+ new TestSemantics(
+ label: r'B',
+ textDirection: TextDirection.ltr,
+ ),
+ new TestSemantics(
+ label: r'C',
+ textDirection: TextDirection.ltr,
+ ),
+ new TestSemantics(
+ label: r'D',
+ textDirection: TextDirection.ltr,
+ ),
+ new TestSemantics(
+ flags: <SemanticsFlags>[SemanticsFlags.isButton],
+ actions: <SemanticsAction>[SemanticsAction.tap],
+ label: r'Show accounts',
+ textDirection: TextDirection.ltr,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ignoreId: true, ignoreTransform: true, ignoreRect: true,
+ ),
+ );
+
+ semantics.dispose();
+ });
+
+ testWidgets('UserAccountsDrawerHeader provides semantics with missing properties', (WidgetTester tester) async {
+ final SemanticsTester semantics = new SemanticsTester(tester);
+ await pumpTestWidget(
+ tester,
+ withEmail: false,
+ withName: false,
+ withOnDetailsPressedHandler: false,
+ );
+
+ expect(
+ semantics,
+ hasSemantics(
+ new TestSemantics(
+ children: <TestSemantics>[
+ new TestSemantics(
+ label: 'Signed in',
+ textDirection: TextDirection.ltr,
+ children: <TestSemantics>[
+ new TestSemantics(
+ label: r'B',
+ textDirection: TextDirection.ltr,
+ ),
+ new TestSemantics(
+ label: r'C',
+ textDirection: TextDirection.ltr,
+ ),
+ new TestSemantics(
+ label: r'D',
+ textDirection: TextDirection.ltr,
+ ),
+ ],
+ ),
+ ],
+ ),
+ ignoreId: true, ignoreTransform: true, ignoreRect: true,
+ ),
+ );
+
+ semantics.dispose();
+ });
}