| // 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'; |
| |
| void main() { |
| // Pumps and ensures that the BottomSheet animates non-linearly. |
| Future<void> checkNonLinearAnimation(WidgetTester tester) async { |
| final Offset firstPosition = tester.getCenter(find.text('One')); |
| await tester.pump(const Duration(milliseconds: 30)); |
| final Offset secondPosition = tester.getCenter(find.text('One')); |
| await tester.pump(const Duration(milliseconds: 30)); |
| final Offset thirdPosition = tester.getCenter(find.text('One')); |
| |
| final double dyDelta1 = secondPosition.dy - firstPosition.dy; |
| final double dyDelta2 = thirdPosition.dy - secondPosition.dy; |
| |
| // If the animation were linear, these two values would be the same. |
| expect(dyDelta1, isNot(moreOrLessEquals(dyDelta2, epsilon: 0.1))); |
| } |
| |
| testWidgets('Persistent draggableScrollableSheet localHistoryEntries test', (WidgetTester tester) async { |
| // Regression test for https://github.com/flutter/flutter/issues/110123 |
| Widget buildFrame(Widget? bottomSheet) { |
| return MaterialApp( |
| home: Scaffold( |
| appBar: AppBar(), |
| body: const Center(child: Text('body')), |
| bottomSheet: bottomSheet, |
| floatingActionButton: const FloatingActionButton( |
| onPressed: null, |
| child: Text('fab'), |
| ), |
| ), |
| ); |
| } |
| final Widget draggableScrollableSheet = DraggableScrollableSheet( |
| expand: false, |
| snap: true, |
| initialChildSize: 0.3, |
| minChildSize: 0.3, |
| builder: (_, ScrollController controller) { |
| return ListView.builder( |
| itemExtent: 50.0, |
| itemCount: 50, |
| itemBuilder: (_, int index) => Text('Item $index'), |
| controller: controller, |
| ); |
| }, |
| ); |
| |
| await tester.pumpWidget(buildFrame(draggableScrollableSheet)); |
| await tester.pumpAndSettle(); |
| |
| expect(find.byType(BackButton).hitTestable(), findsNothing); |
| |
| await tester.drag(find.text('Item 2'), const Offset(0, -200.0)); |
| await tester.pumpAndSettle(); |
| // We've started to drag up, we should have a back button now for a11y |
| expect(find.byType(BackButton).hitTestable(), findsOneWidget); |
| |
| await tester.fling(find.text('Item 2'), const Offset(0, 200.0), 2000.0); |
| await tester.pumpAndSettle(); |
| // BackButton should be hidden |
| expect(find.byType(BackButton).hitTestable(), findsNothing); |
| |
| // Show the back button again |
| await tester.drag(find.text('Item 2'), const Offset(0, -200.0)); |
| await tester.pumpAndSettle(); |
| expect(find.byType(BackButton).hitTestable(), findsOneWidget); |
| |
| // Remove the draggableScrollableSheet should hide the back button |
| await tester.pumpWidget(buildFrame(null)); |
| expect(find.byType(BackButton).hitTestable(), findsNothing); |
| }); |
| |
| // Regression test for https://github.com/flutter/flutter/issues/83668 |
| testWidgets('Scaffold.bottomSheet update test', (WidgetTester tester) async { |
| Widget buildFrame(Widget? bottomSheet) { |
| return MaterialApp( |
| home: Scaffold( |
| body: const Placeholder(), |
| bottomSheet: bottomSheet, |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildFrame(const Text('I love Flutter!'))); |
| await tester.pumpWidget(buildFrame(null)); |
| |
| // The disappearing animation has not yet been completed. |
| await tester.pumpWidget(buildFrame(const Text('I love Flutter!'))); |
| }); |
| |
| testWidgets('Verify that a BottomSheet can be rebuilt with ScaffoldFeatureController.setState()', (WidgetTester tester) async { |
| final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); |
| int buildCount = 0; |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Scaffold( |
| key: scaffoldKey, |
| body: const Center(child: Text('body')), |
| ), |
| )); |
| |
| final PersistentBottomSheetController<void> bottomSheet = scaffoldKey.currentState!.showBottomSheet<void>((_) { |
| return Builder( |
| builder: (BuildContext context) { |
| buildCount += 1; |
| return Container(height: 200.0); |
| }, |
| ); |
| }); |
| |
| await tester.pump(); |
| expect(buildCount, equals(1)); |
| bottomSheet.setState!(() { }); |
| await tester.pump(); |
| expect(buildCount, equals(2)); |
| }); |
| |
| testWidgets('Verify that a persistent BottomSheet cannot be dismissed', (WidgetTester tester) async { |
| await tester.pumpWidget(MaterialApp( |
| home: Scaffold( |
| body: const Center(child: Text('body')), |
| bottomSheet: DraggableScrollableSheet( |
| expand: false, |
| builder: (_, ScrollController controller) { |
| return ListView( |
| controller: controller, |
| shrinkWrap: true, |
| children: const <Widget>[ |
| SizedBox(height: 100.0, child: Text('One')), |
| SizedBox(height: 100.0, child: Text('Two')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| ], |
| ); |
| }, |
| ), |
| ), |
| )); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Two'), findsOneWidget); |
| |
| await tester.drag(find.text('Two'), const Offset(0.0, 400.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Two'), findsOneWidget); |
| }); |
| |
| testWidgets('Verify that a scrollable BottomSheet can be dismissed', (WidgetTester tester) async { |
| final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Scaffold( |
| key: scaffoldKey, |
| body: const Center(child: Text('body')), |
| ), |
| )); |
| |
| scaffoldKey.currentState!.showBottomSheet<void>((BuildContext context) { |
| return ListView( |
| shrinkWrap: true, |
| primary: false, |
| children: const <Widget>[ |
| SizedBox(height: 100.0, child: Text('One')), |
| SizedBox(height: 100.0, child: Text('Two')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| ], |
| ); |
| }); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Two'), findsOneWidget); |
| |
| await tester.drag(find.text('Two'), const Offset(0.0, 400.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Two'), findsNothing); |
| }); |
| |
| testWidgets('Verify that a BottomSheet animates non-linearly', (WidgetTester tester) async { |
| final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Scaffold( |
| key: scaffoldKey, |
| body: const Center(child: Text('body')), |
| ), |
| )); |
| |
| scaffoldKey.currentState!.showBottomSheet<void>((BuildContext context) { |
| return ListView( |
| shrinkWrap: true, |
| primary: false, |
| children: const <Widget>[ |
| SizedBox(height: 100.0, child: Text('One')), |
| SizedBox(height: 100.0, child: Text('Two')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| ], |
| ); |
| }); |
| await tester.pump(); |
| await checkNonLinearAnimation(tester); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Two'), findsOneWidget); |
| |
| await tester.drag(find.text('Two'), const Offset(0.0, 200.0)); |
| await checkNonLinearAnimation(tester); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Two'), findsNothing); |
| }); |
| |
| testWidgets('Verify that a scrollControlled BottomSheet can be dismissed', (WidgetTester tester) async { |
| final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Scaffold( |
| key: scaffoldKey, |
| body: const Center(child: Text('body')), |
| ), |
| )); |
| |
| scaffoldKey.currentState!.showBottomSheet<void>( |
| (BuildContext context) { |
| return DraggableScrollableSheet( |
| expand: false, |
| builder: (_, ScrollController controller) { |
| return ListView( |
| shrinkWrap: true, |
| controller: controller, |
| children: const <Widget>[ |
| SizedBox(height: 100.0, child: Text('One')), |
| SizedBox(height: 100.0, child: Text('Two')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| ], |
| ); |
| }, |
| ); |
| }, |
| ); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Two'), findsOneWidget); |
| |
| await tester.drag(find.text('Two'), const Offset(0.0, 400.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Two'), findsNothing); |
| }); |
| |
| testWidgets('Verify that a persistent BottomSheet can fling up and hide the fab', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| appBar: AppBar(), |
| body: const Center(child: Text('body')), |
| bottomSheet: DraggableScrollableSheet( |
| expand: false, |
| builder: (_, ScrollController controller) { |
| return ListView.builder( |
| itemExtent: 50.0, |
| itemCount: 50, |
| itemBuilder: (_, int index) => Text('Item $index'), |
| controller: controller, |
| ); |
| }, |
| ), |
| floatingActionButton: const FloatingActionButton( |
| onPressed: null, |
| child: Text('fab'), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Item 2'), findsOneWidget); |
| expect(find.text('Item 22'), findsNothing); |
| expect(find.byType(FloatingActionButton), findsOneWidget); |
| expect(find.byType(FloatingActionButton).hitTestable(), findsOneWidget); |
| expect(find.byType(BackButton).hitTestable(), findsNothing); |
| |
| await tester.drag(find.text('Item 2'), const Offset(0, -20.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Item 2'), findsOneWidget); |
| expect(find.text('Item 22'), findsNothing); |
| expect(find.byType(FloatingActionButton), findsOneWidget); |
| expect(find.byType(FloatingActionButton).hitTestable(), findsOneWidget); |
| |
| await tester.fling(find.text('Item 2'), const Offset(0.0, -600.0), 2000.0); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Item 2'), findsNothing); |
| expect(find.text('Item 22'), findsOneWidget); |
| expect(find.byType(FloatingActionButton), findsOneWidget); |
| expect(find.byType(FloatingActionButton).hitTestable(), findsNothing); |
| }); |
| |
| testWidgets('Verify that a back button resets a persistent BottomSheet', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| appBar: AppBar(), |
| body: const Center(child: Text('body')), |
| bottomSheet: DraggableScrollableSheet( |
| expand: false, |
| builder: (_, ScrollController controller) { |
| return ListView.builder( |
| itemExtent: 50.0, |
| itemCount: 50, |
| itemBuilder: (_, int index) => Text('Item $index'), |
| controller: controller, |
| ); |
| }, |
| ), |
| floatingActionButton: const FloatingActionButton( |
| onPressed: null, |
| child: Text('fab'), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Item 2'), findsOneWidget); |
| expect(find.text('Item 22'), findsNothing); |
| expect(find.byType(BackButton).hitTestable(), findsNothing); |
| |
| await tester.drag(find.text('Item 2'), const Offset(0, -20.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Item 2'), findsOneWidget); |
| expect(find.text('Item 22'), findsNothing); |
| // We've started to drag up, we should have a back button now for a11y |
| expect(find.byType(BackButton).hitTestable(), findsOneWidget); |
| |
| await tester.tap(find.byType(BackButton)); |
| await tester.pumpAndSettle(); |
| |
| expect(find.byType(BackButton).hitTestable(), findsNothing); |
| expect(find.text('Item 2'), findsOneWidget); |
| expect(find.text('Item 22'), findsNothing); |
| |
| await tester.fling(find.text('Item 2'), const Offset(0.0, -600.0), 2000.0); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Item 2'), findsNothing); |
| expect(find.text('Item 22'), findsOneWidget); |
| expect(find.byType(BackButton).hitTestable(), findsOneWidget); |
| |
| await tester.tap(find.byType(BackButton)); |
| await tester.pumpAndSettle(); |
| |
| expect(find.byType(BackButton).hitTestable(), findsNothing); |
| expect(find.text('Item 2'), findsOneWidget); |
| expect(find.text('Item 22'), findsNothing); |
| }); |
| |
| testWidgets('Verify that a scrollable BottomSheet hides the fab when scrolled up', (WidgetTester tester) async { |
| final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Scaffold( |
| key: scaffoldKey, |
| body: const Center(child: Text('body')), |
| floatingActionButton: const FloatingActionButton( |
| onPressed: null, |
| child: Text('fab'), |
| ), |
| ), |
| )); |
| |
| scaffoldKey.currentState!.showBottomSheet<void>( |
| (BuildContext context) { |
| return DraggableScrollableSheet( |
| expand: false, |
| builder: (_, ScrollController controller) { |
| return ListView( |
| controller: controller, |
| shrinkWrap: true, |
| children: const <Widget>[ |
| SizedBox(height: 100.0, child: Text('One')), |
| SizedBox(height: 100.0, child: Text('Two')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| ], |
| ); |
| }, |
| ); |
| }, |
| ); |
| |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Two'), findsOneWidget); |
| expect(find.byType(FloatingActionButton).hitTestable(), findsOneWidget); |
| |
| await tester.drag(find.text('Two'), const Offset(0.0, -600.0)); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Two'), findsOneWidget); |
| expect(find.byType(FloatingActionButton), findsOneWidget); |
| expect(find.byType(FloatingActionButton).hitTestable(), findsNothing); |
| }); |
| |
| testWidgets('showBottomSheet()', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| await tester.pumpWidget(MaterialApp( |
| home: Scaffold( |
| body: Placeholder(key: key), |
| ), |
| )); |
| |
| int buildCount = 0; |
| showBottomSheet<void>( |
| context: key.currentContext!, |
| builder: (BuildContext context) { |
| return Builder( |
| builder: (BuildContext context) { |
| buildCount += 1; |
| return Container(height: 200.0); |
| }, |
| ); |
| }, |
| ); |
| await tester.pump(); |
| expect(buildCount, equals(1)); |
| }); |
| |
| testWidgets('Scaffold removes top MediaQuery padding', (WidgetTester tester) async { |
| late BuildContext scaffoldContext; |
| late BuildContext bottomSheetContext; |
| |
| await tester.pumpWidget(MaterialApp( |
| home: MediaQuery( |
| data: const MediaQueryData( |
| padding: EdgeInsets.all(50.0), |
| ), |
| child: Scaffold( |
| resizeToAvoidBottomInset: false, |
| body: Builder( |
| builder: (BuildContext context) { |
| scaffoldContext = context; |
| return Container(); |
| }, |
| ), |
| ), |
| ), |
| )); |
| |
| await tester.pump(); |
| |
| showBottomSheet<void>( |
| context: scaffoldContext, |
| builder: (BuildContext context) { |
| bottomSheetContext = context; |
| return Container(); |
| }, |
| ); |
| |
| await tester.pump(); |
| |
| expect( |
| MediaQuery.of(bottomSheetContext).padding, |
| const EdgeInsets.only( |
| bottom: 50.0, |
| left: 50.0, |
| right: 50.0, |
| ), |
| ); |
| }); |
| |
| testWidgets('Scaffold.bottomSheet', (WidgetTester tester) async { |
| final Key bottomSheetKey = UniqueKey(); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Scaffold( |
| body: const Placeholder(), |
| bottomSheet: Container( |
| key: bottomSheetKey, |
| alignment: Alignment.center, |
| height: 200.0, |
| child: Builder( |
| builder: (BuildContext context) { |
| return ElevatedButton( |
| child: const Text('showModalBottomSheet'), |
| onPressed: () { |
| showModalBottomSheet<void>( |
| context: context, |
| builder: (BuildContext context) => const Text('modal bottom sheet'), |
| ); |
| }, |
| ); |
| }, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(find.text('showModalBottomSheet'), findsOneWidget); |
| expect(tester.getSize(find.byKey(bottomSheetKey)), const Size(800.0, 200.0)); |
| expect(tester.getTopLeft(find.byKey(bottomSheetKey)), const Offset(0.0, 400.0)); |
| |
| // Show the modal bottomSheet |
| await tester.tap(find.text('showModalBottomSheet')); |
| await tester.pumpAndSettle(); |
| expect(find.text('modal bottom sheet'), findsOneWidget); |
| |
| // Dismiss the modal bottomSheet by tapping above the sheet |
| await tester.tapAt(const Offset(20.0, 20.0)); |
| await tester.pumpAndSettle(); |
| expect(find.text('modal bottom sheet'), findsNothing); |
| expect(find.text('showModalBottomSheet'), findsOneWidget); |
| |
| // Remove the persistent bottomSheet |
| await tester.pumpWidget( |
| const MaterialApp( |
| home: Scaffold( |
| body: Placeholder(), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| expect(find.text('showModalBottomSheet'), findsNothing); |
| expect(find.byKey(bottomSheetKey), findsNothing); |
| }); |
| |
| // Regression test for https://github.com/flutter/flutter/issues/71435 |
| testWidgets( |
| 'Scaffold.bottomSheet should be updated without creating a new RO' |
| ' when the new widget has the same key and type.', |
| (WidgetTester tester) async { |
| Widget buildFrame(String text) { |
| return MaterialApp( |
| home: Scaffold( |
| body: const Placeholder(), |
| bottomSheet: Text(text), |
| ), |
| ); |
| } |
| |
| await tester.pumpWidget(buildFrame('I love Flutter!')); |
| final RenderParagraph renderBeforeUpdate = tester.renderObject(find.text('I love Flutter!')); |
| |
| await tester.pumpWidget(buildFrame('Flutter is the best!')); |
| await tester.pumpAndSettle(); |
| final RenderParagraph renderAfterUpdate = tester.renderObject(find.text('Flutter is the best!')); |
| |
| expect(renderBeforeUpdate, renderAfterUpdate); |
| }, |
| ); |
| |
| testWidgets('Verify that visual properties are passed through', (WidgetTester tester) async { |
| final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>(); |
| const Color color = Colors.pink; |
| const double elevation = 9.0; |
| const ShapeBorder shape = BeveledRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))); |
| const Clip clipBehavior = Clip.antiAlias; |
| |
| await tester.pumpWidget(MaterialApp( |
| home: Scaffold( |
| key: scaffoldKey, |
| body: const Center(child: Text('body')), |
| ), |
| )); |
| |
| scaffoldKey.currentState!.showBottomSheet<void>((BuildContext context) { |
| return ListView( |
| shrinkWrap: true, |
| primary: false, |
| children: const <Widget>[ |
| SizedBox(height: 100.0, child: Text('One')), |
| SizedBox(height: 100.0, child: Text('Two')), |
| SizedBox(height: 100.0, child: Text('Three')), |
| ], |
| ); |
| }, backgroundColor: color, elevation: elevation, shape: shape, clipBehavior: clipBehavior); |
| |
| await tester.pumpAndSettle(); |
| |
| final BottomSheet bottomSheet = tester.widget(find.byType(BottomSheet)); |
| expect(bottomSheet.backgroundColor, color); |
| expect(bottomSheet.elevation, elevation); |
| expect(bottomSheet.shape, shape); |
| expect(bottomSheet.clipBehavior, clipBehavior); |
| }); |
| |
| testWidgets('PersistentBottomSheetController.close dismisses the bottom sheet', (WidgetTester tester) async { |
| final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey(); |
| await tester.pumpWidget(MaterialApp( |
| home: Scaffold( |
| key: scaffoldKey, |
| body: const Center(child: Text('body')), |
| ), |
| )); |
| |
| final PersistentBottomSheetController<void> bottomSheet = scaffoldKey.currentState!.showBottomSheet<void>((_) { |
| return Builder( |
| builder: (BuildContext context) { |
| return Container(height: 200.0); |
| }, |
| ); |
| }); |
| |
| await tester.pump(); |
| expect(find.byType(BottomSheet), findsOneWidget); |
| |
| bottomSheet.close(); |
| await tester.pump(); |
| expect(find.byType(BottomSheet), findsNothing); |
| }); |
| } |