// Copyright 2014 The Flutter 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 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; const Key avatarA = Key('A'); const Key avatarC = Key('C'); const Key avatarD = Key('D'); Future<void> pumpTestWidget( WidgetTester tester, { bool withName = true, bool withEmail = true, bool withOnDetailsPressedHandler = true, Size otherAccountsPictureSize = const Size.square(40.0), Size currentAccountPictureSize = const Size.square(72.0), Color? primaryColor, Color? colorSchemePrimary, }) async { await tester.pumpWidget( MaterialApp( theme: ThemeData( primaryColor: primaryColor, colorScheme: const ColorScheme.light().copyWith(primary: colorSchemePrimary), ), home: MediaQuery( data: const MediaQueryData( padding: EdgeInsets.only( left: 10.0, top: 20.0, right: 30.0, bottom: 40.0, ), ), child: Material( child: Center( child: UserAccountsDrawerHeader( onDetailsPressed: withOnDetailsPressedHandler ? () { } : null, currentAccountPictureSize: currentAccountPictureSize, otherAccountsPicturesSize: otherAccountsPictureSize, currentAccountPicture: const ExcludeSemantics( child: CircleAvatar( key: avatarA, child: Text('A'), ), ), otherAccountsPictures: const <Widget>[ CircleAvatar( child: Text('B'), ), CircleAvatar( key: avatarC, child: Text('C'), ), CircleAvatar( key: avatarD, child: Text('D'), ), CircleAvatar( child: Text('E'), ), ], accountName: withName ? const Text('name') : null, accountEmail: withEmail ? const Text('email') : null, ), ), ), ), ), ); } void main() { // Find the exact transform which is the descendant of [UserAccountsDrawerHeader]. final Finder findTransform = find.descendant( of: find.byType(UserAccountsDrawerHeader), matching: find.byType(Transform), ); testWidgets('UserAccountsDrawerHeader inherits ColorScheme.primary', (WidgetTester tester) async { const Color primaryColor = Color(0xff00ff00); const Color colorSchemePrimary = Color(0xff0000ff); await pumpTestWidget(tester, primaryColor: primaryColor, colorSchemePrimary: colorSchemePrimary); final BoxDecoration? boxDecoration = tester.widget<DrawerHeader>( find.byType(DrawerHeader), ).decoration as BoxDecoration?; expect(boxDecoration?.color == primaryColor, false); expect(boxDecoration?.color == colorSchemePrimary, true); }); testWidgets('UserAccountsDrawerHeader test', (WidgetTester tester) async { await pumpTestWidget(tester); expect(find.text('A'), findsOneWidget); expect(find.text('B'), findsOneWidget); expect(find.text('C'), findsOneWidget); expect(find.text('D'), findsOneWidget); expect(find.text('E'), findsNothing); expect(find.text('name'), findsOneWidget); expect(find.text('email'), findsOneWidget); RenderBox box = tester.renderObject(find.byKey(avatarA)); expect(box.size.width, equals(72.0)); expect(box.size.height, equals(72.0)); box = tester.renderObject(find.byKey(avatarC)); expect(box.size.width, equals(40.0)); expect(box.size.height, equals(40.0)); // Verify height = height + top padding + bottom margin + bottom edge) box = tester.renderObject(find.byType(UserAccountsDrawerHeader)); expect(box.size.height, equals(160.0 + 20.0 + 8.0 + 1.0)); final Offset topLeft = tester.getTopLeft(find.byType(UserAccountsDrawerHeader)); final Offset topRight = tester.getTopRight(find.byType(UserAccountsDrawerHeader)); final Offset avatarATopLeft = tester.getTopLeft(find.byKey(avatarA)); final Offset avatarDTopRight = tester.getTopRight(find.byKey(avatarD)); final Offset avatarCTopRight = tester.getTopRight(find.byKey(avatarC)); expect(avatarATopLeft.dx - topLeft.dx, equals(16.0 + 10.0)); // left padding expect(avatarATopLeft.dy - topLeft.dy, equals(16.0 + 20.0)); // add top padding expect(topRight.dx - avatarDTopRight.dx, equals(16.0 + 30.0)); // right padding expect(avatarDTopRight.dy - topRight.dy, equals(16.0 + 20.0)); // add top padding expect(avatarDTopRight.dx - avatarCTopRight.dx, equals(40.0 + 16.0)); // size + space between }); testWidgets('UserAccountsDrawerHeader change default size test', (WidgetTester tester) async { const Size currentAccountPictureSize = Size.square(60.0); const Size otherAccountsPictureSize = Size.square(30.0); await pumpTestWidget( tester, currentAccountPictureSize: currentAccountPictureSize, otherAccountsPictureSize: otherAccountsPictureSize, ); final RenderBox currentAccountRenderBox = tester.renderObject(find.byKey(avatarA)); final RenderBox otherAccountRenderBox = tester.renderObject(find.byKey(avatarC)); expect(currentAccountRenderBox.size, currentAccountPictureSize); expect(otherAccountRenderBox.size, otherAccountsPictureSize); }); testWidgets('UserAccountsDrawerHeader icon rotation test', (WidgetTester tester) async { await pumpTestWidget(tester); Transform transformWidget = tester.firstWidget(findTransform); // Icon is right side up. expect(transformWidget.transform.getRotation()[0], 1.0); expect(transformWidget.transform.getRotation()[4], 1.0); await tester.tap(find.byType(Icon)); await tester.pump(); await tester.pump(const Duration(milliseconds: 10)); expect(tester.hasRunningAnimations, isTrue); await tester.pumpAndSettle(); await tester.pump(); transformWidget = tester.firstWidget(findTransform); // Icon has rotated 180 degrees. expect(transformWidget.transform.getRotation()[0], -1.0); expect(transformWidget.transform.getRotation()[4], -1.0); await tester.tap(find.byType(Icon)); await tester.pump(); await tester.pump(const Duration(milliseconds: 10)); expect(tester.hasRunningAnimations, isTrue); await tester.pumpAndSettle(); await tester.pump(); transformWidget = tester.firstWidget(findTransform); // Icon has rotated 180 degrees back to the original position. expect(transformWidget.transform.getRotation()[0], 1.0); expect(transformWidget.transform.getRotation()[4], 1.0); }); // Regression test for https://github.com/flutter/flutter/issues/25801. testWidgets('UserAccountsDrawerHeader icon does not rotate after setState', (WidgetTester tester) async { late StateSetter testSetState; await tester.pumpWidget(MaterialApp( home: Material( child: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { testSetState = setState; return UserAccountsDrawerHeader( onDetailsPressed: () { }, accountName: const Text('name'), accountEmail: const Text('email'), ); }, ), ), )); Transform transformWidget = tester.firstWidget(findTransform); // Icon is right side up. expect(transformWidget.transform.getRotation()[0], 1.0); expect(transformWidget.transform.getRotation()[4], 1.0); testSetState(() { }); await tester.pump(const Duration(milliseconds: 10)); expect(tester.hasRunningAnimations, isFalse); expect(await tester.pumpAndSettle(), 1); transformWidget = tester.firstWidget(findTransform); // Icon has not rotated. expect(transformWidget.transform.getRotation()[0], 1.0); expect(transformWidget.transform.getRotation()[4], 1.0); }); testWidgets('UserAccountsDrawerHeader icon rotation test speeeeeedy', (WidgetTester tester) async { await pumpTestWidget(tester); Transform transformWidget = tester.firstWidget(findTransform); // Icon is right side up. expect(transformWidget.transform.getRotation()[0], 1.0); expect(transformWidget.transform.getRotation()[4], 1.0); // Icon starts to rotate down. await tester.tap(find.byType(Icon)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(tester.hasRunningAnimations, isTrue); // Icon starts to rotate up mid animation. await tester.tap(find.byType(Icon)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(tester.hasRunningAnimations, isTrue); // Icon starts to rotate down again still mid animation. await tester.tap(find.byType(Icon)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(tester.hasRunningAnimations, isTrue); // Icon starts to rotate up to its original position mid animation. await tester.tap(find.byType(Icon)); await tester.pump(); await tester.pump(const Duration(milliseconds: 100)); expect(tester.hasRunningAnimations, isTrue); await tester.pumpAndSettle(); await tester.pump(); transformWidget = tester.firstWidget(findTransform); // Icon has rotated 180 degrees back to the original position. expect(transformWidget.transform.getRotation()[0], 1.0); expect(transformWidget.transform.getRotation()[4], 1.0); }); testWidgets('UserAccountsDrawerHeader icon color changes', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Material( child: UserAccountsDrawerHeader( onDetailsPressed: () {}, accountName: const Text('name'), accountEmail: const Text('email'), ), ), )); Icon iconWidget = tester.firstWidget(find.byType(Icon)); // Default icon color is white. expect(iconWidget.color, Colors.white); const Color arrowColor = Colors.red; await tester.pumpWidget(MaterialApp( home: Material( child: UserAccountsDrawerHeader( onDetailsPressed: () { }, accountName: const Text('name'), accountEmail: const Text('email'), arrowColor: arrowColor, ), ), )); iconWidget = tester.firstWidget(find.byType(Icon)); expect(iconWidget.color, arrowColor); }); testWidgets('UserAccountsDrawerHeader null parameters LTR', (WidgetTester tester) async { Widget buildFrame({ Widget? currentAccountPicture, List<Widget>? otherAccountsPictures, Widget? accountName, Widget? accountEmail, VoidCallback? onDetailsPressed, EdgeInsets? margin, }) { return MaterialApp( home: Material( child: Center( child: 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, lessThan(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, lessThan(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, lessThan(tester.getCenter(find.byType(Icon)).dx), ); expect( tester.getBottomLeft(find.text('accountEmail')).dy, greaterThan(tester.getBottomLeft(find.text('accountName')).dy), ); expect( tester.getBottomLeft(find.text('accountEmail')).dx, tester.getBottomLeft(find.text('accountName')).dx, ); await tester.pumpWidget(buildFrame( currentAccountPicture: const CircleAvatar(child: Text('A')), )); expect(find.text('A'), findsOneWidget); await tester.pumpWidget(buildFrame( otherAccountsPictures: <Widget>[const CircleAvatar(child: Text('A'))], )); expect(find.text('A'), findsOneWidget); const Key avatarA = Key('A'); await tester.pumpWidget(buildFrame( currentAccountPicture: const CircleAvatar(key: avatarA, child: Text('A')), accountName: const Text('accountName'), )); expect( tester.getBottomLeft(find.byKey(avatarA)).dx, tester.getBottomLeft(find.text('accountName')).dx, ); expect( tester.getBottomLeft(find.text('accountName')).dy, 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 MaterialApp( home: Directionality( textDirection: TextDirection.rtl, child: Material( child: Center( child: 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: Text('A')), )); expect(find.text('A'), findsOneWidget); await tester.pumpWidget(buildFrame( otherAccountsPictures: <Widget>[const CircleAvatar(child: Text('A'))], )); expect(find.text('A'), findsOneWidget); const Key avatarA = Key('A'); await tester.pumpWidget(buildFrame( currentAccountPicture: const CircleAvatar(key: avatarA, child: 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 = SemanticsTester(tester); await pumpTestWidget(tester); expect( semantics, hasSemantics( TestSemantics( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.isFocusable], label: 'Signed in\nname\nemail', textDirection: TextDirection.ltr, children: <TestSemantics>[ TestSemantics( label: r'B', textDirection: TextDirection.ltr, ), TestSemantics( label: r'C', textDirection: TextDirection.ltr, ), TestSemantics( label: r'D', textDirection: TextDirection.ltr, ), TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.isButton], actions: <SemanticsAction>[SemanticsAction.tap], label: r'Show accounts', textDirection: TextDirection.ltr, ), ], ), ], ), ], ), ], ), ], ), ignoreId: true, ignoreTransform: true, ignoreRect: true, ), ); semantics.dispose(); }); testWidgets('alternative account selectors have sufficient tap targets', (WidgetTester tester) async { final SemanticsHandle handle = tester.ensureSemantics(); await pumpTestWidget(tester); expect(tester.getSemantics(find.text('B')), matchesSemantics( label: 'B', size: const Size(48.0, 48.0), )); expect(tester.getSemantics(find.text('C')), matchesSemantics( label: 'C', size: const Size(48.0, 48.0), )); expect(tester.getSemantics(find.text('D')), matchesSemantics( label: 'D', size: const Size(48.0, 48.0), )); handle.dispose(); }); testWidgets('UserAccountsDrawerHeader provides semantics with missing properties', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await pumpTestWidget( tester, withEmail: false, withName: false, withOnDetailsPressedHandler: false, ); expect( semantics, hasSemantics( TestSemantics( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( children: <TestSemantics>[ TestSemantics( flags: <SemanticsFlag>[SemanticsFlag.scopesRoute], children: <TestSemantics>[ TestSemantics( label: 'Signed in', textDirection: TextDirection.ltr, children: <TestSemantics>[ TestSemantics( label: r'B', textDirection: TextDirection.ltr, ), TestSemantics( label: r'C', textDirection: TextDirection.ltr, ), TestSemantics( label: r'D', textDirection: TextDirection.ltr, ), ], ), ], ), ], ), ], ), ], ), ignoreId: true, ignoreTransform: true, ignoreRect: true, ), ); semantics.dispose(); }); }