| // Copyright 2015 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 'package:flutter/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:flutter/gestures.dart' show DragStartBehavior; |
| |
| import '../widgets/semantics_tester.dart'; |
| |
| void main() { |
| testWidgets('Scaffold control test', (WidgetTester tester) async { |
| final Key bodyKey = UniqueKey(); |
| Widget boilerplate(Widget child) { |
| return Localizations( |
| locale: const Locale('en', 'us'), |
| delegates: const <LocalizationsDelegate<dynamic>>[ |
| DefaultWidgetsLocalizations.delegate, |
| DefaultMaterialLocalizations.delegate, |
| ], |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: child, |
| ), |
| ); |
| } |
| await tester.pumpWidget(boilerplate(Scaffold( |
| appBar: AppBar(title: const Text('Title')), |
| body: Container(key: bodyKey), |
| ), |
| )); |
| expect(tester.takeException(), isFlutterError); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Scaffold( |
| appBar: AppBar(title: const Text('Title')), |
| body: Container(key: bodyKey), |
| ), |
| )); |
| RenderBox bodyBox = tester.renderObject(find.byKey(bodyKey)); |
| expect(bodyBox.size, equals(const Size(800.0, 544.0))); |
| |
| await tester.pumpWidget(boilerplate(MediaQuery( |
| data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)), |
| child: Scaffold( |
| appBar: AppBar(title: const Text('Title')), |
| body: Container(key: bodyKey), |
| ), |
| ), |
| )); |
| |
| bodyBox = tester.renderObject(find.byKey(bodyKey)); |
| expect(bodyBox.size, equals(const Size(800.0, 444.0))); |
| |
| await tester.pumpWidget(boilerplate(MediaQuery( |
| data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)), |
| child: Scaffold( |
| appBar: AppBar(title: const Text('Title')), |
| body: Container(key: bodyKey), |
| resizeToAvoidBottomInset: false, |
| ), |
| ))); |
| |
| bodyBox = tester.renderObject(find.byKey(bodyKey)); |
| expect(bodyBox.size, equals(const Size(800.0, 544.0))); |
| |
| // Backwards compatibility: deprecated resizeToAvoidBottomPadding flag |
| await tester.pumpWidget(boilerplate(MediaQuery( |
| data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)), |
| child: Scaffold( |
| appBar: AppBar(title: const Text('Title')), |
| body: Container(key: bodyKey), |
| resizeToAvoidBottomPadding: false, |
| ), |
| ))); |
| |
| bodyBox = tester.renderObject(find.byKey(bodyKey)); |
| expect(bodyBox.size, equals(const Size(800.0, 544.0))); |
| }); |
| |
| testWidgets('Scaffold large bottom padding test', (WidgetTester tester) async { |
| final Key bodyKey = UniqueKey(); |
| |
| Widget boilerplate(Widget child) { |
| return Localizations( |
| locale: const Locale('en', 'us'), |
| delegates: const <LocalizationsDelegate<dynamic>>[ |
| DefaultWidgetsLocalizations.delegate, |
| DefaultMaterialLocalizations.delegate, |
| ], |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: child, |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(boilerplate(MediaQuery( |
| data: const MediaQueryData( |
| viewInsets: EdgeInsets.only(bottom: 700.0), |
| ), |
| child: Scaffold( |
| body: Container(key: bodyKey), |
| ), |
| ))); |
| |
| final RenderBox bodyBox = tester.renderObject(find.byKey(bodyKey)); |
| expect(bodyBox.size, equals(const Size(800.0, 0.0))); |
| |
| await tester.pumpWidget(boilerplate(MediaQuery( |
| data: const MediaQueryData( |
| viewInsets: EdgeInsets.only(bottom: 500.0), |
| ), |
| child: Scaffold( |
| body: Container(key: bodyKey), |
| ), |
| ), |
| )); |
| |
| expect(bodyBox.size, equals(const Size(800.0, 100.0))); |
| |
| await tester.pumpWidget(boilerplate(MediaQuery( |
| data: const MediaQueryData( |
| viewInsets: EdgeInsets.only(bottom: 580.0), |
| ), |
| child: Scaffold( |
| appBar: AppBar( |
| title: const Text('Title'), |
| ), |
| body: Container(key: bodyKey), |
| ), |
| ), |
| )); |
| |
| expect(bodyBox.size, equals(const Size(800.0, 0.0))); |
| }); |
| |
| testWidgets('Floating action entrance/exit animation', (WidgetTester tester) async { |
| await tester.pumpWidget(const MaterialApp(home: Scaffold( |
| floatingActionButton: FloatingActionButton( |
| key: Key('one'), |
| onPressed: null, |
| child: Text('1'), |
| ), |
| ))); |
| |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| await tester.pumpWidget(const MaterialApp(home: Scaffold( |
| floatingActionButton: FloatingActionButton( |
| key: Key('two'), |
| onPressed: null, |
| child: Text('2'), |
| ), |
| ))); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| await tester.pumpWidget(Container()); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| await tester.pumpWidget(const MaterialApp(home: Scaffold())); |
| |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| await tester.pumpWidget(const MaterialApp(home: Scaffold( |
| floatingActionButton: FloatingActionButton( |
| key: Key('one'), |
| onPressed: null, |
| child: Text('1'), |
| ), |
| ))); |
| |
| expect(tester.binding.transientCallbackCount, greaterThan(0)); |
| }); |
| |
| testWidgets('Floating action button directionality', (WidgetTester tester) async { |
| Widget build(TextDirection textDirection) { |
| return Directionality( |
| textDirection: textDirection, |
| child: const MediaQuery( |
| data: MediaQueryData( |
| viewInsets: EdgeInsets.only(bottom: 200.0), |
| ), |
| child: Scaffold( |
| floatingActionButton: FloatingActionButton( |
| onPressed: null, |
| child: Text('1'), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(build(TextDirection.ltr)); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 356.0)); |
| |
| await tester.pumpWidget(build(TextDirection.rtl)); |
| expect(tester.binding.transientCallbackCount, 0); |
| |
| expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(44.0, 356.0)); |
| }); |
| |
| testWidgets('Floating Action Button bottom padding not consumed by viewInsets', (WidgetTester tester) async { |
| final Widget child = Directionality( |
| textDirection: TextDirection.ltr, |
| child: Scaffold( |
| resizeToAvoidBottomInset: false, |
| body: Container(), |
| floatingActionButton: const Placeholder(), |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.only(bottom: 20.0), |
| ), |
| child: child, |
| ), |
| ); |
| final Offset initialPoint = tester.getCenter(find.byType(Placeholder)); |
| // Consume bottom padding - as if by the keyboard opening |
| await tester.pumpWidget( |
| MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.zero, |
| viewPadding: EdgeInsets.only(bottom: 20), |
| viewInsets: EdgeInsets.only(bottom: 300), |
| ), |
| child: child, |
| ), |
| ); |
| final Offset finalPoint = tester.getCenter(find.byType(Placeholder)); |
| expect(initialPoint, finalPoint); |
| }); |
| |
| testWidgets('Drawer scrolling', (WidgetTester tester) async { |
| final Key drawerKey = UniqueKey(); |
| const double appBarHeight = 256.0; |
| |
| final ScrollController scrollOffset = ScrollController(); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| drawer: Drawer( |
| key: drawerKey, |
| child: ListView( |
| dragStartBehavior: DragStartBehavior.down, |
| controller: scrollOffset, |
| children: List<Widget>.generate(10, |
| (int index) => SizedBox(height: 100.0, child: Text('D$index')), |
| ), |
| ), |
| ), |
| body: CustomScrollView( |
| slivers: <Widget>[ |
| const SliverAppBar( |
| pinned: true, |
| expandedHeight: appBarHeight, |
| title: Text('Title'), |
| flexibleSpace: FlexibleSpaceBar(title: Text('Title')), |
| ), |
| SliverPadding( |
| padding: const EdgeInsets.only(top: appBarHeight), |
| sliver: SliverList( |
| delegate: SliverChildListDelegate(List<Widget>.generate( |
| 10, (int index) => SizedBox(height: 100.0, child: Text('B$index')), |
| )), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| final ScaffoldState state = tester.firstState(find.byType(Scaffold)); |
| state.openDrawer(); |
| |
| await tester.pump(); |
| await tester.pump(const Duration(seconds: 1)); |
| |
| expect(scrollOffset.offset, 0.0); |
| |
| const double scrollDelta = 80.0; |
| await tester.drag(find.byKey(drawerKey), const Offset(0.0, -scrollDelta)); |
| await tester.pump(); |
| |
| expect(scrollOffset.offset, scrollDelta); |
| |
| final RenderBox renderBox = tester.renderObject(find.byType(AppBar)); |
| expect(renderBox.size.height, equals(appBarHeight)); |
| }); |
| |
| Widget _buildStatusBarTestApp(TargetPlatform platform) { |
| return MaterialApp( |
| theme: ThemeData(platform: platform), |
| home: MediaQuery( |
| data: const MediaQueryData(padding: EdgeInsets.only(top: 25.0)), // status bar |
| child: Scaffold( |
| body: CustomScrollView( |
| primary: true, |
| slivers: <Widget>[ |
| const SliverAppBar( |
| title: Text('Title'), |
| ), |
| SliverList( |
| delegate: SliverChildListDelegate(List<Widget>.generate( |
| 20, (int index) => SizedBox(height: 100.0, child: Text('$index')), |
| )), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| testWidgets('Tapping the status bar scrolls to top on iOS', (WidgetTester tester) async { |
| await tester.pumpWidget(_buildStatusBarTestApp(TargetPlatform.iOS)); |
| final ScrollableState scrollable = tester.state(find.byType(Scrollable)); |
| scrollable.position.jumpTo(500.0); |
| expect(scrollable.position.pixels, equals(500.0)); |
| await tester.tapAt(const Offset(100.0, 10.0)); |
| await tester.pumpAndSettle(); |
| expect(scrollable.position.pixels, equals(0.0)); |
| }); |
| |
| testWidgets('Tapping the status bar does not scroll to top on Android', (WidgetTester tester) async { |
| await tester.pumpWidget(_buildStatusBarTestApp(TargetPlatform.android)); |
| final ScrollableState scrollable = tester.state(find.byType(Scrollable)); |
| scrollable.position.jumpTo(500.0); |
| expect(scrollable.position.pixels, equals(500.0)); |
| await tester.tapAt(const Offset(100.0, 10.0)); |
| await tester.pump(); |
| await tester.pump(const Duration(seconds: 1)); |
| expect(scrollable.position.pixels, equals(500.0)); |
| }); |
| |
| testWidgets('Bottom sheet cannot overlap app bar', (WidgetTester tester) async { |
| final Key sheetKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.android), |
| home: Scaffold( |
| appBar: AppBar( |
| title: const Text('Title'), |
| ), |
| body: Builder( |
| builder: (BuildContext context) { |
| return GestureDetector( |
| onTap: () { |
| Scaffold.of(context).showBottomSheet<void>((BuildContext context) { |
| return Container( |
| key: sheetKey, |
| color: Colors.blue[500], |
| ); |
| }); |
| }, |
| child: const Text('X'), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| await tester.tap(find.text('X')); |
| await tester.pump(); // start animation |
| await tester.pump(const Duration(seconds: 1)); |
| |
| final RenderBox appBarBox = tester.renderObject(find.byType(AppBar)); |
| final RenderBox sheetBox = tester.renderObject(find.byKey(sheetKey)); |
| |
| final Offset appBarBottomRight = appBarBox.localToGlobal(appBarBox.size.bottomRight(Offset.zero)); |
| final Offset sheetTopRight = sheetBox.localToGlobal(sheetBox.size.topRight(Offset.zero)); |
| |
| expect(appBarBottomRight, equals(sheetTopRight)); |
| }); |
| |
| testWidgets('BottomSheet bottom padding is not consumed by viewInsets', (WidgetTester tester) async { |
| final Widget child = Directionality( |
| textDirection: TextDirection.ltr, |
| child: Scaffold( |
| resizeToAvoidBottomInset: false, |
| body: Container(), |
| bottomSheet: const Placeholder(), |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.only(bottom: 20.0), |
| ), |
| child: child, |
| ), |
| ); |
| final Offset initialPoint = tester.getCenter(find.byType(Placeholder)); |
| // Consume bottom padding - as if by the keyboard opening |
| await tester.pumpWidget( |
| MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.zero, |
| viewPadding: EdgeInsets.only(bottom: 20), |
| viewInsets: EdgeInsets.only(bottom: 300), |
| ), |
| child: child, |
| ), |
| ); |
| final Offset finalPoint = tester.getCenter(find.byType(Placeholder)); |
| expect(initialPoint, finalPoint); |
| }); |
| |
| testWidgets('Persistent bottom buttons are persistent', (WidgetTester tester) async { |
| bool didPressButton = false; |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| body: SingleChildScrollView( |
| child: Container( |
| color: Colors.amber[500], |
| height: 5000.0, |
| child: const Text('body'), |
| ), |
| ), |
| persistentFooterButtons: <Widget>[ |
| FlatButton( |
| onPressed: () { |
| didPressButton = true; |
| }, |
| child: const Text('X'), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.drag(find.text('body'), const Offset(0.0, -1000.0)); |
| expect(didPressButton, isFalse); |
| await tester.tap(find.text('X')); |
| expect(didPressButton, isTrue); |
| }); |
| |
| testWidgets('Persistent bottom buttons apply media padding', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.fromLTRB(10.0, 20.0, 30.0, 40.0), |
| ), |
| child: Scaffold( |
| body: SingleChildScrollView( |
| child: Container( |
| color: Colors.amber[500], |
| height: 5000.0, |
| child: const Text('body'), |
| ), |
| ), |
| persistentFooterButtons: const <Widget>[Placeholder()], |
| ), |
| ), |
| ), |
| ); |
| expect(tester.getBottomLeft(find.byType(ButtonBar)), const Offset(10.0, 560.0)); |
| expect(tester.getBottomRight(find.byType(ButtonBar)), const Offset(770.0, 560.0)); |
| }); |
| |
| testWidgets('Persistent bottom buttons bottom padding is not consumed by viewInsets', (WidgetTester tester) async { |
| final Widget child = Directionality( |
| textDirection: TextDirection.ltr, |
| child: Scaffold( |
| resizeToAvoidBottomInset: false, |
| body: Container(), |
| persistentFooterButtons: const <Widget>[Placeholder()], |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.only(bottom: 20.0), |
| ), |
| child: child, |
| ), |
| ); |
| final Offset initialPoint = tester.getCenter(find.byType(Placeholder)); |
| // Consume bottom padding - as if by the keyboard opening |
| await tester.pumpWidget( |
| MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.zero, |
| viewPadding: EdgeInsets.only(bottom: 20), |
| viewInsets: EdgeInsets.only(bottom: 300), |
| ), |
| child: child, |
| ), |
| ); |
| final Offset finalPoint = tester.getCenter(find.byType(Placeholder)); |
| expect(initialPoint, finalPoint); |
| }); |
| |
| group('back arrow', () { |
| Future<void> expectBackIcon(WidgetTester tester, TargetPlatform platform, IconData expectedIcon) async { |
| final GlobalKey rootKey = GlobalKey(); |
| final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{ |
| '/': (_) => Container(key: rootKey, child: const Text('Home')), |
| '/scaffold': (_) => Scaffold( |
| appBar: AppBar(), |
| body: const Text('Scaffold'), |
| ), |
| }; |
| await tester.pumpWidget( |
| MaterialApp(theme: ThemeData(platform: platform), routes: routes) |
| ); |
| |
| Navigator.pushNamed(rootKey.currentContext, '/scaffold'); |
| await tester.pump(); |
| await tester.pump(const Duration(seconds: 1)); |
| |
| final Icon icon = tester.widget(find.byType(Icon)); |
| expect(icon.icon, expectedIcon); |
| } |
| |
| testWidgets('Back arrow uses correct default on Android', (WidgetTester tester) async { |
| await expectBackIcon(tester, TargetPlatform.android, Icons.arrow_back); |
| }); |
| |
| testWidgets('Back arrow uses correct default on Fuchsia', (WidgetTester tester) async { |
| await expectBackIcon(tester, TargetPlatform.fuchsia, Icons.arrow_back); |
| }); |
| |
| testWidgets('Back arrow uses correct default on iOS', (WidgetTester tester) async { |
| await expectBackIcon(tester, TargetPlatform.iOS, Icons.arrow_back_ios); |
| }); |
| }); |
| |
| group('close button', () { |
| Future<void> expectCloseIcon(WidgetTester tester, TargetPlatform platform, IconData expectedIcon, PageRoute<void> routeBuilder()) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: platform), |
| home: Scaffold(appBar: AppBar(), body: const Text('Page 1')), |
| ), |
| ); |
| |
| tester.state<NavigatorState>(find.byType(Navigator)).push(routeBuilder()); |
| |
| await tester.pump(); |
| await tester.pump(const Duration(seconds: 1)); |
| |
| final Icon icon = tester.widget(find.byType(Icon)); |
| expect(icon.icon, expectedIcon); |
| expect(find.byType(CloseButton), findsOneWidget); |
| } |
| |
| PageRoute<void> materialRouteBuilder() { |
| return MaterialPageRoute<void>( |
| builder: (BuildContext context) { |
| return Scaffold(appBar: AppBar(), body: const Text('Page 2')); |
| }, |
| fullscreenDialog: true, |
| ); |
| } |
| |
| PageRoute<void> pageRouteBuilder() { |
| return PageRouteBuilder<void>( |
| pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { |
| return Scaffold(appBar: AppBar(), body: const Text('Page 2')); |
| }, |
| fullscreenDialog: true, |
| ); |
| } |
| |
| PageRoute<void> customPageRouteBuilder() { |
| return _CustomPageRoute<void>( |
| builder: (BuildContext context) { |
| return Scaffold(appBar: AppBar(), body: const Text('Page 2')); |
| }, |
| fullscreenDialog: true, |
| ); |
| } |
| |
| testWidgets('Close button shows correctly on Android', (WidgetTester tester) async { |
| await expectCloseIcon(tester, TargetPlatform.android, Icons.close, materialRouteBuilder); |
| }); |
| |
| testWidgets('Close button shows correctly on Fuchsia', (WidgetTester tester) async { |
| await expectCloseIcon(tester, TargetPlatform.fuchsia, Icons.close, materialRouteBuilder); |
| }); |
| |
| testWidgets('Close button shows correctly on iOS', (WidgetTester tester) async { |
| await expectCloseIcon(tester, TargetPlatform.iOS, Icons.close, materialRouteBuilder); |
| }); |
| |
| testWidgets('Close button shows correctly with PageRouteBuilder on Android', (WidgetTester tester) async { |
| await expectCloseIcon(tester, TargetPlatform.android, Icons.close, pageRouteBuilder); |
| }); |
| |
| testWidgets('Close button shows correctly with PageRouteBuilder on Fuchsia', (WidgetTester tester) async { |
| await expectCloseIcon(tester, TargetPlatform.fuchsia, Icons.close, pageRouteBuilder); |
| }); |
| |
| testWidgets('Close button shows correctly with PageRouteBuilder on iOS', (WidgetTester tester) async { |
| await expectCloseIcon(tester, TargetPlatform.iOS, Icons.close, pageRouteBuilder); |
| }); |
| |
| testWidgets('Close button shows correctly with custom page route on Android', (WidgetTester tester) async { |
| await expectCloseIcon(tester, TargetPlatform.android, Icons.close, customPageRouteBuilder); |
| }); |
| |
| testWidgets('Close button shows correctly with custom page route on Fuchsia', (WidgetTester tester) async { |
| await expectCloseIcon(tester, TargetPlatform.fuchsia, Icons.close, customPageRouteBuilder); |
| }); |
| |
| testWidgets('Close button shows correctly with custom page route on iOS', (WidgetTester tester) async { |
| await expectCloseIcon(tester, TargetPlatform.iOS, Icons.close, customPageRouteBuilder); |
| }); |
| }); |
| |
| group('body size', () { |
| testWidgets('body size with container', (WidgetTester tester) async { |
| final Key testKey = UniqueKey(); |
| await tester.pumpWidget(Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(), |
| child: Scaffold( |
| body: Container( |
| key: testKey, |
| ), |
| ), |
| ), |
| )); |
| expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 600.0)); |
| expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0)); |
| }); |
| |
| testWidgets('body size with sized container', (WidgetTester tester) async { |
| final Key testKey = UniqueKey(); |
| await tester.pumpWidget(Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(), |
| child: Scaffold( |
| body: Container( |
| key: testKey, |
| height: 100.0, |
| ), |
| ), |
| ), |
| )); |
| expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 100.0)); |
| expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0)); |
| }); |
| |
| testWidgets('body size with centered container', (WidgetTester tester) async { |
| final Key testKey = UniqueKey(); |
| await tester.pumpWidget(Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(), |
| child: Scaffold( |
| body: Center( |
| child: Container( |
| key: testKey, |
| ), |
| ), |
| ), |
| ), |
| )); |
| expect(tester.element(find.byKey(testKey)).size, const Size(800.0, 600.0)); |
| expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0)); |
| }); |
| |
| testWidgets('body size with button', (WidgetTester tester) async { |
| final Key testKey = UniqueKey(); |
| await tester.pumpWidget(Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(), |
| child: Scaffold( |
| body: FlatButton( |
| key: testKey, |
| onPressed: () { }, |
| child: const Text(''), |
| ), |
| ), |
| ), |
| )); |
| expect(tester.element(find.byKey(testKey)).size, const Size(88.0, 48.0)); |
| expect(tester.renderObject<RenderBox>(find.byKey(testKey)).localToGlobal(Offset.zero), const Offset(0.0, 0.0)); |
| }); |
| |
| testWidgets('body size with extendBody', (WidgetTester tester) async { |
| final Key bodyKey = UniqueKey(); |
| double mediaQueryBottom; |
| |
| Widget buildFrame({ bool extendBody, bool resizeToAvoidBottomInset, double viewInsetBottom = 0.0 }) { |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: MediaQueryData( |
| viewInsets: EdgeInsets.only(bottom: viewInsetBottom), |
| ), |
| child: Scaffold( |
| resizeToAvoidBottomInset: resizeToAvoidBottomInset, |
| extendBody: extendBody, |
| body: Builder( |
| builder: (BuildContext context) { |
| mediaQueryBottom = MediaQuery.of(context).padding.bottom; |
| return Container(key: bodyKey); |
| }, |
| ), |
| bottomNavigationBar: const BottomAppBar( |
| child: SizedBox(height: 48.0,), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildFrame(extendBody: true)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); |
| expect(mediaQueryBottom, 48.0); |
| |
| await tester.pumpWidget(buildFrame(extendBody: false)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 552.0)); // 552 = 600 - 48 (BAB height) |
| expect(mediaQueryBottom, 0.0); |
| |
| // If resizeToAvoidBottomInsets is false, same results as if it was unspecified (null). |
| await tester.pumpWidget(buildFrame(extendBody: true, resizeToAvoidBottomInset: false, viewInsetBottom: 100.0)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); |
| expect(mediaQueryBottom, 48.0); |
| |
| await tester.pumpWidget(buildFrame(extendBody: false, resizeToAvoidBottomInset: false, viewInsetBottom: 100.0)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 552.0)); |
| expect(mediaQueryBottom, 0.0); |
| |
| // If resizeToAvoidBottomInsets is true and viewInsets.bottom is > the bottom |
| // navigation bar's height then the body always resizes and the MediaQuery |
| // isn't adjusted. This case corresponds to the keyboard appearing. |
| await tester.pumpWidget(buildFrame(extendBody: true, resizeToAvoidBottomInset: true, viewInsetBottom: 100.0)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); |
| expect(mediaQueryBottom, 0.0); |
| |
| await tester.pumpWidget(buildFrame(extendBody: false, resizeToAvoidBottomInset: true, viewInsetBottom: 100.0)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); |
| expect(mediaQueryBottom, 0.0); |
| }); |
| |
| testWidgets('body size with extendBodyBehindAppBar', (WidgetTester tester) async { |
| final Key appBarKey = UniqueKey(); |
| final Key bodyKey = UniqueKey(); |
| |
| const double appBarHeight = 100; |
| const double windowPaddingTop = 24; |
| bool fixedHeightAppBar; |
| double mediaQueryTop; |
| |
| Widget buildFrame({ bool extendBodyBehindAppBar, bool hasAppBar }) { |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.only(top: windowPaddingTop), |
| ), |
| child: Builder( |
| builder: (BuildContext context) { |
| return Scaffold( |
| extendBodyBehindAppBar: extendBodyBehindAppBar, |
| appBar: !hasAppBar ? null : PreferredSize( |
| key: appBarKey, |
| preferredSize: const Size.fromHeight(appBarHeight), |
| child: Container( |
| constraints: BoxConstraints( |
| minHeight: appBarHeight, |
| maxHeight: fixedHeightAppBar ? appBarHeight : double.infinity, |
| ), |
| ), |
| ), |
| body: Builder( |
| builder: (BuildContext context) { |
| mediaQueryTop = MediaQuery.of(context).padding.top; |
| return Container(key: bodyKey); |
| } |
| ), |
| ); |
| }, |
| ), |
| ), |
| ); |
| } |
| |
| fixedHeightAppBar = false; |
| |
| // When an appbar is provided, the Scaffold's body is built within a |
| // MediaQuery with padding.top = 0, and the appBar's maxHeight is |
| // constrained to its preferredSize.height + the original MediaQuery |
| // padding.top. When extendBodyBehindAppBar is true, an additional |
| // inner MediaQuery is added around the Scaffold's body with padding.top |
| // equal to the overall height of the appBar. See _BodyBuilder in |
| // material/scaffold.dart. |
| |
| await tester.pumpWidget(buildFrame(extendBodyBehindAppBar: true, hasAppBar: true)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); |
| expect(tester.getSize(find.byKey(appBarKey)), const Size(800.0, appBarHeight + windowPaddingTop)); |
| expect(mediaQueryTop, appBarHeight + windowPaddingTop); |
| |
| await tester.pumpWidget(buildFrame(extendBodyBehindAppBar: true, hasAppBar: false)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); |
| expect(find.byKey(appBarKey), findsNothing); |
| expect(mediaQueryTop, windowPaddingTop); |
| |
| await tester.pumpWidget(buildFrame(extendBodyBehindAppBar: false, hasAppBar: true)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0 - appBarHeight - windowPaddingTop)); |
| expect(tester.getSize(find.byKey(appBarKey)), const Size(800.0, appBarHeight + windowPaddingTop)); |
| expect(mediaQueryTop, 0.0); |
| |
| await tester.pumpWidget(buildFrame(extendBodyBehindAppBar: false, hasAppBar: false)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); |
| expect(find.byKey(appBarKey), findsNothing); |
| expect(mediaQueryTop, windowPaddingTop); |
| |
| fixedHeightAppBar = true; |
| |
| await tester.pumpWidget(buildFrame(extendBodyBehindAppBar: true, hasAppBar: true)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); |
| expect(tester.getSize(find.byKey(appBarKey)), const Size(800.0, appBarHeight)); |
| expect(mediaQueryTop, appBarHeight); |
| |
| await tester.pumpWidget(buildFrame(extendBodyBehindAppBar: true, hasAppBar: false)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); |
| expect(find.byKey(appBarKey), findsNothing); |
| expect(mediaQueryTop, windowPaddingTop); |
| |
| await tester.pumpWidget(buildFrame(extendBodyBehindAppBar: false, hasAppBar: true)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0 - appBarHeight)); |
| expect(tester.getSize(find.byKey(appBarKey)), const Size(800.0, appBarHeight)); |
| expect(mediaQueryTop, 0.0); |
| |
| await tester.pumpWidget(buildFrame(extendBodyBehindAppBar: false, hasAppBar: false)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); |
| expect(find.byKey(appBarKey), findsNothing); |
| expect(mediaQueryTop, windowPaddingTop); |
| }); |
| }); |
| |
| testWidgets('Open drawer hides underlying semantics tree', (WidgetTester tester) async { |
| const String bodyLabel = 'I am the body'; |
| const String persistentFooterButtonLabel = 'a button on the bottom'; |
| const String bottomNavigationBarLabel = 'a bar in an app'; |
| const String floatingActionButtonLabel = 'I float in space'; |
| const String drawerLabel = 'I am the reason for this test'; |
| |
| final SemanticsTester semantics = SemanticsTester(tester); |
| await tester.pumpWidget(const MaterialApp(home: Scaffold( |
| body: Text(bodyLabel), |
| persistentFooterButtons: <Widget>[Text(persistentFooterButtonLabel)], |
| bottomNavigationBar: Text(bottomNavigationBarLabel), |
| floatingActionButton: Text(floatingActionButtonLabel), |
| drawer: Drawer(child: Text(drawerLabel)), |
| ))); |
| |
| expect(semantics, includesNodeWith(label: bodyLabel)); |
| expect(semantics, includesNodeWith(label: persistentFooterButtonLabel)); |
| expect(semantics, includesNodeWith(label: bottomNavigationBarLabel)); |
| expect(semantics, includesNodeWith(label: floatingActionButtonLabel)); |
| expect(semantics, isNot(includesNodeWith(label: drawerLabel))); |
| |
| final ScaffoldState state = tester.firstState(find.byType(Scaffold)); |
| state.openDrawer(); |
| await tester.pump(); |
| await tester.pump(const Duration(seconds: 1)); |
| |
| expect(semantics, isNot(includesNodeWith(label: bodyLabel))); |
| expect(semantics, isNot(includesNodeWith(label: persistentFooterButtonLabel))); |
| expect(semantics, isNot(includesNodeWith(label: bottomNavigationBarLabel))); |
| expect(semantics, isNot(includesNodeWith(label: floatingActionButtonLabel))); |
| expect(semantics, includesNodeWith(label: drawerLabel)); |
| |
| semantics.dispose(); |
| }); |
| |
| testWidgets('Scaffold and extreme window padding', (WidgetTester tester) async { |
| final Key appBar = UniqueKey(); |
| final Key body = UniqueKey(); |
| final Key floatingActionButton = UniqueKey(); |
| final Key persistentFooterButton = UniqueKey(); |
| final Key drawer = UniqueKey(); |
| final Key bottomNavigationBar = UniqueKey(); |
| final Key insideAppBar = UniqueKey(); |
| final Key insideBody = UniqueKey(); |
| final Key insideFloatingActionButton = UniqueKey(); |
| final Key insidePersistentFooterButton = UniqueKey(); |
| final Key insideDrawer = UniqueKey(); |
| final Key insideBottomNavigationBar = UniqueKey(); |
| await tester.pumpWidget( |
| Localizations( |
| locale: const Locale('en', 'us'), |
| delegates: const <LocalizationsDelegate<dynamic>>[ |
| DefaultWidgetsLocalizations.delegate, |
| DefaultMaterialLocalizations.delegate, |
| ], |
| child: Directionality( |
| textDirection: TextDirection.rtl, |
| child: MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.only( |
| left: 20.0, |
| top: 30.0, |
| right: 50.0, |
| bottom: 60.0, |
| ), |
| viewInsets: EdgeInsets.only(bottom: 200.0), |
| ), |
| child: Scaffold( |
| drawerDragStartBehavior: DragStartBehavior.down, |
| appBar: PreferredSize( |
| preferredSize: const Size(11.0, 13.0), |
| child: Container( |
| key: appBar, |
| child: SafeArea( |
| child: Placeholder(key: insideAppBar), |
| ), |
| ), |
| ), |
| body: Container( |
| key: body, |
| child: SafeArea( |
| child: Placeholder(key: insideBody), |
| ), |
| ), |
| floatingActionButton: SizedBox( |
| key: floatingActionButton, |
| width: 77.0, |
| height: 77.0, |
| child: SafeArea( |
| child: Placeholder(key: insideFloatingActionButton), |
| ), |
| ), |
| persistentFooterButtons: <Widget>[ |
| SizedBox( |
| key: persistentFooterButton, |
| width: 100.0, |
| height: 90.0, |
| child: SafeArea( |
| child: Placeholder(key: insidePersistentFooterButton), |
| ), |
| ), |
| ], |
| drawer: Container( |
| key: drawer, |
| width: 204.0, |
| child: SafeArea( |
| child: Placeholder(key: insideDrawer), |
| ), |
| ), |
| bottomNavigationBar: SizedBox( |
| key: bottomNavigationBar, |
| height: 85.0, |
| child: SafeArea( |
| child: Placeholder(key: insideBottomNavigationBar), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| // open drawer |
| await tester.flingFrom(const Offset(795.0, 5.0), const Offset(-200.0, 0.0), 10.0); |
| await tester.pump(); |
| await tester.pump(const Duration(seconds: 1)); |
| |
| expect(tester.getRect(find.byKey(appBar)), const Rect.fromLTRB(0.0, 0.0, 800.0, 43.0)); |
| expect(tester.getRect(find.byKey(body)), const Rect.fromLTRB(0.0, 43.0, 800.0, 348.0)); |
| expect(tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 255.0, 113.0, 332.0))); |
| expect(tester.getRect(find.byKey(persistentFooterButton)),const Rect.fromLTRB(28.0, 357.0, 128.0, 447.0)); // Note: has 8px each top/bottom padding. |
| expect(tester.getRect(find.byKey(drawer)), const Rect.fromLTRB(596.0, 0.0, 800.0, 600.0)); |
| expect(tester.getRect(find.byKey(bottomNavigationBar)), const Rect.fromLTRB(0.0, 515.0, 800.0, 600.0)); |
| expect(tester.getRect(find.byKey(insideAppBar)), const Rect.fromLTRB(20.0, 30.0, 750.0, 43.0)); |
| expect(tester.getRect(find.byKey(insideBody)), const Rect.fromLTRB(20.0, 43.0, 750.0, 348.0)); |
| expect(tester.getRect(find.byKey(insideFloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 255.0, 113.0, 332.0))); |
| expect(tester.getRect(find.byKey(insidePersistentFooterButton)), const Rect.fromLTRB(28.0, 357.0, 128.0, 447.0)); |
| expect(tester.getRect(find.byKey(insideDrawer)), const Rect.fromLTRB(596.0, 30.0, 750.0, 540.0)); |
| expect(tester.getRect(find.byKey(insideBottomNavigationBar)), const Rect.fromLTRB(20.0, 515.0, 750.0, 540.0)); |
| }); |
| |
| testWidgets('Scaffold and extreme window padding - persistent footer buttons only', (WidgetTester tester) async { |
| final Key appBar = UniqueKey(); |
| final Key body = UniqueKey(); |
| final Key floatingActionButton = UniqueKey(); |
| final Key persistentFooterButton = UniqueKey(); |
| final Key drawer = UniqueKey(); |
| final Key insideAppBar = UniqueKey(); |
| final Key insideBody = UniqueKey(); |
| final Key insideFloatingActionButton = UniqueKey(); |
| final Key insidePersistentFooterButton = UniqueKey(); |
| final Key insideDrawer = UniqueKey(); |
| await tester.pumpWidget( |
| Localizations( |
| locale: const Locale('en', 'us'), |
| delegates: const <LocalizationsDelegate<dynamic>>[ |
| DefaultWidgetsLocalizations.delegate, |
| DefaultMaterialLocalizations.delegate, |
| ], |
| child: Directionality( |
| textDirection: TextDirection.rtl, |
| child: MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.only( |
| left: 20.0, |
| top: 30.0, |
| right: 50.0, |
| bottom: 60.0, |
| ), |
| viewInsets: EdgeInsets.only(bottom: 200.0), |
| ), |
| child: Scaffold( |
| appBar: PreferredSize( |
| preferredSize: const Size(11.0, 13.0), |
| child: Container( |
| key: appBar, |
| child: SafeArea( |
| child: Placeholder(key: insideAppBar), |
| ), |
| ), |
| ), |
| body: Container( |
| key: body, |
| child: SafeArea( |
| child: Placeholder(key: insideBody), |
| ), |
| ), |
| floatingActionButton: SizedBox( |
| key: floatingActionButton, |
| width: 77.0, |
| height: 77.0, |
| child: SafeArea( |
| child: Placeholder(key: insideFloatingActionButton), |
| ), |
| ), |
| persistentFooterButtons: <Widget>[ |
| SizedBox( |
| key: persistentFooterButton, |
| width: 100.0, |
| height: 90.0, |
| child: SafeArea( |
| child: Placeholder(key: insidePersistentFooterButton), |
| ), |
| ), |
| ], |
| drawer: Container( |
| key: drawer, |
| width: 204.0, |
| child: SafeArea( |
| child: Placeholder(key: insideDrawer), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| // open drawer |
| await tester.flingFrom(const Offset(795.0, 5.0), const Offset(-200.0, 0.0), 10.0); |
| await tester.pump(); |
| await tester.pump(const Duration(seconds: 1)); |
| |
| expect(tester.getRect(find.byKey(appBar)), const Rect.fromLTRB(0.0, 0.0, 800.0, 43.0)); |
| expect(tester.getRect(find.byKey(body)), const Rect.fromLTRB(0.0, 43.0, 800.0, 400.0)); |
| expect(tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 307.0, 113.0, 384.0))); |
| expect(tester.getRect(find.byKey(persistentFooterButton)), const Rect.fromLTRB(28.0, 442.0, 128.0, 532.0)); // Note: has 8px each top/bottom padding. |
| expect(tester.getRect(find.byKey(drawer)), const Rect.fromLTRB(596.0, 0.0, 800.0, 600.0)); |
| expect(tester.getRect(find.byKey(insideAppBar)), const Rect.fromLTRB(20.0, 30.0, 750.0, 43.0)); |
| expect(tester.getRect(find.byKey(insideBody)), const Rect.fromLTRB(20.0, 43.0, 750.0, 400.0)); |
| expect(tester.getRect(find.byKey(insideFloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTRB(36.0, 307.0, 113.0, 384.0))); |
| expect(tester.getRect(find.byKey(insidePersistentFooterButton)), const Rect.fromLTRB(28.0, 442.0, 128.0, 532.0)); |
| expect(tester.getRect(find.byKey(insideDrawer)), const Rect.fromLTRB(596.0, 30.0, 750.0, 540.0)); |
| }); |
| |
| |
| group('ScaffoldGeometry', () { |
| testWidgets('bottomNavigationBar', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(MaterialApp(home: Scaffold( |
| body: Container(), |
| bottomNavigationBar: ConstrainedBox( |
| key: key, |
| constraints: const BoxConstraints.expand(height: 80.0), |
| child: _GeometryListener(), |
| ), |
| ))); |
| |
| final RenderBox navigationBox = tester.renderObject(find.byKey(key)); |
| final RenderBox appBox = tester.renderObject(find.byType(MaterialApp)); |
| final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); |
| final ScaffoldGeometry geometry = listenerState.cache.value; |
| |
| expect( |
| geometry.bottomNavigationBarTop, |
| appBox.size.height - navigationBox.size.height, |
| ); |
| }); |
| |
| testWidgets('no bottomNavigationBar', (WidgetTester tester) async { |
| await tester.pumpWidget(MaterialApp(home: Scaffold( |
| body: ConstrainedBox( |
| constraints: const BoxConstraints.expand(height: 80.0), |
| child: _GeometryListener(), |
| ), |
| ))); |
| |
| final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); |
| final ScaffoldGeometry geometry = listenerState.cache.value; |
| |
| expect( |
| geometry.bottomNavigationBarTop, |
| null, |
| ); |
| }); |
| |
| testWidgets('Scaffold BottomNavigationBar bottom padding is not consumed by viewInsets.', (WidgetTester tester) async { |
| Widget boilerplate(Widget child) { |
| return Localizations( |
| locale: const Locale('en', 'us'), |
| delegates: const <LocalizationsDelegate<dynamic>>[ |
| DefaultWidgetsLocalizations.delegate, |
| DefaultMaterialLocalizations.delegate, |
| ], |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: child, |
| ), |
| ); |
| } |
| |
| final Widget child = boilerplate( |
| Scaffold( |
| resizeToAvoidBottomInset: false, |
| body: const Placeholder(), |
| bottomNavigationBar: BottomNavigationBar( |
| items: const <BottomNavigationBarItem>[ |
| BottomNavigationBarItem( |
| icon: Icon(Icons.add), |
| title: Text('test'), |
| ), |
| BottomNavigationBarItem( |
| icon: Icon(Icons.add), |
| title: Text('test'), |
| ), |
| ], |
| ), |
| ), |
| ); |
| |
| await tester.pumpWidget( |
| MediaQuery( |
| data: const MediaQueryData(padding: EdgeInsets.only(bottom: 20.0)), |
| child: child, |
| ), |
| ); |
| final Offset initialPoint = tester.getCenter(find.byType(Placeholder)); |
| // Consume bottom padding - as if by the keyboard opening |
| await tester.pumpWidget( |
| MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.zero, |
| viewPadding: EdgeInsets.only(bottom: 20), |
| viewInsets: EdgeInsets.only(bottom: 300), |
| ), |
| child: child, |
| ), |
| ); |
| final Offset finalPoint = tester.getCenter(find.byType(Placeholder)); |
| expect(initialPoint, finalPoint); |
| }); |
| |
| testWidgets('floatingActionButton', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(MaterialApp(home: Scaffold( |
| body: Container(), |
| floatingActionButton: FloatingActionButton( |
| key: key, |
| child: _GeometryListener(), |
| onPressed: () { }, |
| ), |
| ))); |
| |
| final RenderBox floatingActionButtonBox = tester.renderObject(find.byKey(key)); |
| final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); |
| final ScaffoldGeometry geometry = listenerState.cache.value; |
| |
| final Rect fabRect = floatingActionButtonBox.localToGlobal(Offset.zero) & floatingActionButtonBox.size; |
| |
| expect( |
| geometry.floatingActionButtonArea, |
| fabRect, |
| ); |
| }); |
| |
| testWidgets('no floatingActionButton', (WidgetTester tester) async { |
| await tester.pumpWidget(MaterialApp(home: Scaffold( |
| body: ConstrainedBox( |
| constraints: const BoxConstraints.expand(height: 80.0), |
| child: _GeometryListener(), |
| ), |
| ))); |
| |
| final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); |
| final ScaffoldGeometry geometry = listenerState.cache.value; |
| |
| expect( |
| geometry.floatingActionButtonArea, |
| null, |
| ); |
| }); |
| |
| testWidgets('floatingActionButton entrance/exit animation', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(MaterialApp(home: Scaffold( |
| body: ConstrainedBox( |
| constraints: const BoxConstraints.expand(height: 80.0), |
| child: _GeometryListener(), |
| ), |
| ))); |
| |
| await tester.pumpWidget(MaterialApp(home: Scaffold( |
| body: Container(), |
| floatingActionButton: FloatingActionButton( |
| key: key, |
| child: _GeometryListener(), |
| onPressed: () { }, |
| ), |
| ))); |
| |
| final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| |
| ScaffoldGeometry geometry = listenerState.cache.value; |
| final Rect transitioningFabRect = geometry.floatingActionButtonArea; |
| |
| final double transitioningRotation = tester.widget<RotationTransition>( |
| find.byType(RotationTransition), |
| ).turns.value; |
| |
| await tester.pump(const Duration(seconds: 3)); |
| geometry = listenerState.cache.value; |
| final RenderBox floatingActionButtonBox = tester.renderObject(find.byKey(key)); |
| final Rect fabRect = floatingActionButtonBox.localToGlobal(Offset.zero) & floatingActionButtonBox.size; |
| |
| final double completedRotation = tester.widget<RotationTransition>( |
| find.byType(RotationTransition), |
| ).turns.value; |
| |
| expect(transitioningRotation, lessThan(1.0)); |
| |
| expect(completedRotation, equals(1.0)); |
| |
| expect( |
| geometry.floatingActionButtonArea, |
| fabRect, |
| ); |
| |
| expect( |
| geometry.floatingActionButtonArea.center, |
| transitioningFabRect.center, |
| ); |
| |
| expect( |
| geometry.floatingActionButtonArea.width, |
| greaterThan(transitioningFabRect.width), |
| ); |
| |
| expect( |
| geometry.floatingActionButtonArea.height, |
| greaterThan(transitioningFabRect.height), |
| ); |
| }); |
| |
| testWidgets('change notifications', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| int numNotificationsAtLastFrame = 0; |
| await tester.pumpWidget(MaterialApp(home: Scaffold( |
| body: ConstrainedBox( |
| constraints: const BoxConstraints.expand(height: 80.0), |
| child: _GeometryListener(), |
| ), |
| ))); |
| |
| final _GeometryListenerState listenerState = tester.state(find.byType(_GeometryListener)); |
| |
| expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame)); |
| numNotificationsAtLastFrame = listenerState.numNotifications; |
| |
| await tester.pumpWidget(MaterialApp(home: Scaffold( |
| body: Container(), |
| floatingActionButton: FloatingActionButton( |
| key: key, |
| child: _GeometryListener(), |
| onPressed: () { }, |
| ), |
| ))); |
| |
| expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame)); |
| numNotificationsAtLastFrame = listenerState.numNotifications; |
| |
| await tester.pump(const Duration(milliseconds: 50)); |
| |
| expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame)); |
| numNotificationsAtLastFrame = listenerState.numNotifications; |
| |
| await tester.pump(const Duration(seconds: 3)); |
| |
| expect(listenerState.numNotifications, greaterThan(numNotificationsAtLastFrame)); |
| numNotificationsAtLastFrame = listenerState.numNotifications; |
| }); |
| |
| testWidgets('Simultaneous drawers on either side', (WidgetTester tester) async { |
| const String bodyLabel = 'I am the body'; |
| const String drawerLabel = 'I am the label on start side'; |
| const String endDrawerLabel = 'I am the label on end side'; |
| |
| final SemanticsTester semantics = SemanticsTester(tester); |
| await tester.pumpWidget(const MaterialApp(home: Scaffold( |
| body: Text(bodyLabel), |
| drawer: Drawer(child: Text(drawerLabel)), |
| endDrawer: Drawer(child: Text(endDrawerLabel)), |
| ))); |
| |
| expect(semantics, includesNodeWith(label: bodyLabel)); |
| expect(semantics, isNot(includesNodeWith(label: drawerLabel))); |
| expect(semantics, isNot(includesNodeWith(label: endDrawerLabel))); |
| |
| final ScaffoldState state = tester.firstState(find.byType(Scaffold)); |
| state.openDrawer(); |
| await tester.pump(); |
| await tester.pump(const Duration(seconds: 1)); |
| |
| expect(semantics, isNot(includesNodeWith(label: bodyLabel))); |
| expect(semantics, includesNodeWith(label: drawerLabel)); |
| |
| state.openEndDrawer(); |
| await tester.pump(); |
| await tester.pump(const Duration(seconds: 1)); |
| |
| expect(semantics, isNot(includesNodeWith(label: bodyLabel))); |
| expect(semantics, includesNodeWith(label: endDrawerLabel)); |
| |
| semantics.dispose(); |
| }); |
| |
| testWidgets('Drawer state query correctly', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: SafeArea( |
| left: false, |
| top: true, |
| right: false, |
| bottom: false, |
| child: Scaffold( |
| endDrawer: const Drawer( |
| child: Text('endDrawer'), |
| ), |
| drawer: const Drawer( |
| child: Text('drawer'), |
| ), |
| body: const Text('scaffold body'), |
| appBar: AppBar( |
| centerTitle: true, |
| title: const Text('Title'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); |
| |
| final Finder drawerOpenButton = find.byType(IconButton).first; |
| final Finder endDrawerOpenButton = find.byType(IconButton).last; |
| |
| await tester.tap(drawerOpenButton); |
| await tester.pumpAndSettle(); |
| expect(true, scaffoldState.isDrawerOpen); |
| await tester.tap(endDrawerOpenButton); |
| await tester.pumpAndSettle(); |
| expect(false, scaffoldState.isDrawerOpen); |
| |
| await tester.tap(endDrawerOpenButton); |
| await tester.pumpAndSettle(); |
| expect(true, scaffoldState.isEndDrawerOpen); |
| await tester.tap(drawerOpenButton); |
| await tester.pumpAndSettle(); |
| expect(false, scaffoldState.isEndDrawerOpen); |
| |
| scaffoldState.openDrawer(); |
| expect(true, scaffoldState.isDrawerOpen); |
| await tester.tap(drawerOpenButton); |
| await tester.pumpAndSettle(); |
| |
| scaffoldState.openEndDrawer(); |
| expect(true, scaffoldState.isEndDrawerOpen); |
| }); |
| |
| testWidgets('Dual Drawer Opening', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: SafeArea( |
| left: false, |
| top: true, |
| right: false, |
| bottom: false, |
| child: Scaffold( |
| endDrawer: const Drawer( |
| child: Text('endDrawer'), |
| ), |
| drawer: const Drawer( |
| child: Text('drawer'), |
| ), |
| body: const Text('scaffold body'), |
| appBar: AppBar( |
| centerTitle: true, |
| title: const Text('Title'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // Open Drawer, tap on end drawer, which closes the drawer, but does |
| // not open the drawer. |
| await tester.tap(find.byType(IconButton).first); |
| await tester.pumpAndSettle(); |
| await tester.tap(find.byType(IconButton).last); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('endDrawer'), findsNothing); |
| expect(find.text('drawer'), findsNothing); |
| |
| // Tapping the first opens the first drawer |
| await tester.tap(find.byType(IconButton).first); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('endDrawer'), findsNothing); |
| expect(find.text('drawer'), findsOneWidget); |
| |
| // Tapping on the end drawer and then on the drawer should close the |
| // drawer and then reopen it. |
| await tester.tap(find.byType(IconButton).last); |
| await tester.pumpAndSettle(); |
| await tester.tap(find.byType(IconButton).first); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('endDrawer'), findsNothing); |
| expect(find.text('drawer'), findsOneWidget); |
| }); |
| |
| testWidgets('Drawer opens correctly with padding from MediaQuery (LTR)', (WidgetTester tester) async { |
| const double simulatedNotchSize = 40.0; |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| drawer: const Drawer( |
| child: Text('Drawer'), |
| ), |
| body: const Text('Scaffold Body'), |
| appBar: AppBar( |
| centerTitle: true, |
| title: const Text('Title'), |
| ), |
| ), |
| ), |
| ); |
| |
| ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); |
| expect(scaffoldState.isDrawerOpen, false); |
| |
| await tester.dragFrom(const Offset(simulatedNotchSize + 15.0, 100), const Offset(300, 0)); |
| await tester.pumpAndSettle(); |
| expect(scaffoldState.isDrawerOpen, false); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.fromLTRB(simulatedNotchSize, 0, 0, 0), |
| ), |
| child: Scaffold( |
| drawer: const Drawer( |
| child: Text('Drawer'), |
| ), |
| body: const Text('Scaffold Body'), |
| appBar: AppBar( |
| centerTitle: true, |
| title: const Text('Title'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| scaffoldState = tester.state(find.byType(Scaffold)); |
| expect(scaffoldState.isDrawerOpen, false); |
| |
| await tester.dragFrom( |
| const Offset(simulatedNotchSize + 15.0, 100), |
| const Offset(300, 0), |
| ); |
| await tester.pumpAndSettle(); |
| expect(scaffoldState.isDrawerOpen, true); |
| }); |
| |
| testWidgets('Drawer opens correctly with padding from MediaQuery (RTL)', (WidgetTester tester) async { |
| const double simulatedNotchSize = 40.0; |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| drawer: const Drawer( |
| child: Text('Drawer'), |
| ), |
| body: const Text('Scaffold Body'), |
| appBar: AppBar( |
| centerTitle: true, |
| title: const Text('Title'), |
| ), |
| ), |
| ), |
| ); |
| |
| final double scaffoldWidth = tester.renderObject<RenderBox>( |
| find.byType(Scaffold), |
| ).size.width; |
| ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); |
| expect(scaffoldState.isDrawerOpen, false); |
| |
| await tester.dragFrom( |
| Offset(scaffoldWidth - simulatedNotchSize - 15.0, 100), |
| const Offset(-300, 0), |
| ); |
| await tester.pumpAndSettle(); |
| expect(scaffoldState.isDrawerOpen, false); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.fromLTRB(0, 0, simulatedNotchSize, 0), |
| ), |
| child: Directionality( |
| textDirection: TextDirection.rtl, |
| child: Scaffold( |
| drawer: const Drawer( |
| child: Text('Drawer'), |
| ), |
| body: const Text('Scaffold body'), |
| appBar: AppBar( |
| centerTitle: true, |
| title: const Text('Title'), |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| scaffoldState = tester.state(find.byType(Scaffold)); |
| expect(scaffoldState.isDrawerOpen, false); |
| |
| await tester.dragFrom( |
| Offset(scaffoldWidth - simulatedNotchSize - 15.0, 100), |
| const Offset(-300, 0), |
| ); |
| await tester.pumpAndSettle(); |
| expect(scaffoldState.isDrawerOpen, true); |
| }); |
| }); |
| |
| testWidgets('Drawer opens correctly with custom edgeDragWidth', (WidgetTester tester) async { |
| // The default edge drag width is 20.0. |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| drawer: const Drawer( |
| child: Text('Drawer'), |
| ), |
| body: const Text('Scaffold body'), |
| appBar: AppBar( |
| centerTitle: true, |
| title: const Text('Title'), |
| ), |
| ), |
| ), |
| ); |
| ScaffoldState scaffoldState = tester.state(find.byType(Scaffold)); |
| expect(scaffoldState.isDrawerOpen, false); |
| |
| await tester.dragFrom(const Offset(35, 100), const Offset(300, 0)); |
| await tester.pumpAndSettle(); |
| expect(scaffoldState.isDrawerOpen, false); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| drawer: const Drawer( |
| child: Text('Drawer'), |
| ), |
| drawerEdgeDragWidth: 40.0, |
| body: const Text('Scaffold Body'), |
| appBar: AppBar( |
| centerTitle: true, |
| title: const Text('Title'), |
| ), |
| ), |
| ), |
| ); |
| scaffoldState = tester.state(find.byType(Scaffold)); |
| expect(scaffoldState.isDrawerOpen, false); |
| |
| await tester.dragFrom(const Offset(35, 100), const Offset(300, 0)); |
| await tester.pumpAndSettle(); |
| expect(scaffoldState.isDrawerOpen, true); |
| }); |
| |
| testWidgets('Nested scaffold body insets', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/20295 |
| final Key bodyKey = UniqueKey(); |
| |
| Widget buildFrame(bool innerResizeToAvoidBottomInset, bool outerResizeToAvoidBottomInset) { |
| return Directionality( |
| textDirection: TextDirection.ltr, |
| child: MediaQuery( |
| data: const MediaQueryData(viewInsets: EdgeInsets.only(bottom: 100.0)), |
| child: Builder( |
| builder: (BuildContext context) { |
| return Scaffold( |
| resizeToAvoidBottomInset: outerResizeToAvoidBottomInset, |
| body: Builder( |
| builder: (BuildContext context) { |
| return Scaffold( |
| resizeToAvoidBottomInset: innerResizeToAvoidBottomInset, |
| body: Container(key: bodyKey), |
| ); |
| }, |
| ), |
| ); |
| }, |
| ), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildFrame(true, true)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); |
| |
| await tester.pumpWidget(buildFrame(false, true)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); |
| |
| await tester.pumpWidget(buildFrame(true, false)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); |
| |
| // This is the only case where the body is not bottom inset. |
| await tester.pumpWidget(buildFrame(false, false)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 600.0)); |
| |
| await tester.pumpWidget(buildFrame(null, null)); // resizeToAvoidBottomInset default is true |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); |
| |
| await tester.pumpWidget(buildFrame(null, false)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); |
| |
| await tester.pumpWidget(buildFrame(false, null)); |
| expect(tester.getSize(find.byKey(bodyKey)), const Size(800.0, 500.0)); |
| }); |
| } |
| |
| class _GeometryListener extends StatefulWidget { |
| @override |
| _GeometryListenerState createState() => _GeometryListenerState(); |
| } |
| |
| class _GeometryListenerState extends State<_GeometryListener> { |
| @override |
| Widget build(BuildContext context) { |
| return CustomPaint( |
| painter: cache |
| ); |
| } |
| |
| int numNotifications = 0; |
| ValueListenable<ScaffoldGeometry> geometryListenable; |
| _GeometryCachePainter cache; |
| |
| @override |
| void didChangeDependencies() { |
| super.didChangeDependencies(); |
| final ValueListenable<ScaffoldGeometry> newListenable = Scaffold.geometryOf(context); |
| if (geometryListenable == newListenable) |
| return; |
| |
| if (geometryListenable != null) |
| geometryListenable.removeListener(onGeometryChanged); |
| |
| geometryListenable = newListenable; |
| geometryListenable.addListener(onGeometryChanged); |
| cache = _GeometryCachePainter(geometryListenable); |
| } |
| |
| void onGeometryChanged() { |
| numNotifications += 1; |
| } |
| } |
| |
| // The Scaffold.geometryOf() value is only available at paint time. |
| // To fetch it for the tests we implement this CustomPainter that just |
| // caches the ScaffoldGeometry value in its paint method. |
| class _GeometryCachePainter extends CustomPainter { |
| _GeometryCachePainter(this.geometryListenable) : super(repaint: geometryListenable); |
| |
| final ValueListenable<ScaffoldGeometry> geometryListenable; |
| |
| ScaffoldGeometry value; |
| @override |
| void paint(Canvas canvas, Size size) { |
| value = geometryListenable.value; |
| } |
| |
| @override |
| bool shouldRepaint(_GeometryCachePainter oldDelegate) { |
| return true; |
| } |
| } |
| |
| class _CustomPageRoute<T> extends PageRoute<T> { |
| _CustomPageRoute({ |
| @required this.builder, |
| RouteSettings settings = const RouteSettings(), |
| this.maintainState = true, |
| bool fullscreenDialog = false, |
| }) : assert(builder != null), |
| super(settings: settings, fullscreenDialog: fullscreenDialog); |
| |
| final WidgetBuilder builder; |
| |
| @override |
| Duration get transitionDuration => const Duration(milliseconds: 300); |
| |
| @override |
| Color get barrierColor => null; |
| |
| @override |
| String get barrierLabel => null; |
| |
| @override |
| final bool maintainState; |
| |
| @override |
| Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { |
| return builder(context); |
| } |
| |
| @override |
| Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) { |
| return child; |
| } |
| } |