| // 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_test/flutter_test.dart'; |
| |
| void main() { |
| testWidgets('Controller expands and collapses the widget', (WidgetTester tester) async { |
| final ExpansibleController controller = ExpansibleController(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Expansible( |
| controller: controller, |
| bodyBuilder: (BuildContext context, Animation<double> animation) => const Text('Body'), |
| headerBuilder: |
| (BuildContext context, Animation<double> animation) => GestureDetector( |
| onTap: controller.isExpanded ? controller.collapse : controller.expand, |
| child: const Text('Header'), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(find.text('Body'), findsNothing); |
| controller.expand(); |
| await tester.pumpAndSettle(); |
| expect(find.text('Body'), findsOneWidget); |
| |
| controller.collapse(); |
| await tester.pumpAndSettle(); |
| expect(find.text('Body'), findsNothing); |
| |
| controller.dispose(); |
| }); |
| |
| testWidgets('Can listen to the expansion state', (WidgetTester tester) async { |
| final ExpansibleController controller = ExpansibleController(); |
| bool? expansionState; |
| controller.addListener(() { |
| expansionState = controller.isExpanded; |
| }); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Expansible( |
| controller: controller, |
| bodyBuilder: (BuildContext context, Animation<double> animation) => const Text('Body'), |
| headerBuilder: |
| (BuildContext context, Animation<double> animation) => GestureDetector( |
| onTap: controller.isExpanded ? controller.collapse : controller.expand, |
| child: const Text('Header'), |
| ), |
| ), |
| ), |
| ); |
| |
| // Tap on the header to toggle the expansion. |
| await tester.tap(find.text('Header')); |
| await tester.pumpAndSettle(); |
| expect(expansionState, true); |
| |
| await tester.tap(find.text('Header')); |
| await tester.pumpAndSettle(); |
| expect(expansionState, false); |
| |
| // Use the controller to toggle the expansion. |
| controller.expand(); |
| await tester.pumpAndSettle(); |
| expect(expansionState, true); |
| |
| controller.collapse(); |
| await tester.pumpAndSettle(); |
| expect(expansionState, false); |
| |
| controller.dispose(); |
| }); |
| |
| testWidgets('Can set expansible to be initially expanded', (WidgetTester tester) async { |
| final ExpansibleController controller = ExpansibleController(); |
| controller.expand(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: SingleChildScrollView( |
| child: Column( |
| children: <Widget>[ |
| Expansible( |
| controller: controller, |
| bodyBuilder: |
| (BuildContext context, Animation<double> animation) => const Text('Body'), |
| headerBuilder: |
| (BuildContext context, Animation<double> animation) => GestureDetector( |
| onTap: controller.isExpanded ? controller.collapse : controller.expand, |
| child: const Text('Header'), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| expect(find.text('Body'), findsOneWidget); |
| |
| await tester.tap(find.text('Header')); |
| await tester.pumpAndSettle(); |
| |
| expect(find.text('Body'), findsNothing); |
| |
| controller.dispose(); |
| }); |
| |
| testWidgets('Can compose header and body with expansibleBuilder', (WidgetTester tester) async { |
| final ExpansibleController controller = ExpansibleController(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Expansible( |
| controller: controller, |
| bodyBuilder: (BuildContext context, Animation<double> animation) => const Text('Body'), |
| headerBuilder: |
| (BuildContext context, Animation<double> animation) => GestureDetector( |
| onTap: controller.isExpanded ? controller.collapse : controller.expand, |
| child: const Text('Header'), |
| ), |
| expansibleBuilder: ( |
| BuildContext context, |
| Widget header, |
| Widget body, |
| Animation<double> animation, |
| ) { |
| return header; |
| }, |
| ), |
| ), |
| ); |
| |
| // Tap on the header to toggle the expansion. |
| await tester.tap(find.text('Header')); |
| await tester.pumpAndSettle(); |
| expect(find.text('Header'), findsOneWidget); |
| expect(find.text('Body'), findsNothing); |
| |
| await tester.tap(find.text('Header')); |
| await tester.pumpAndSettle(); |
| expect(find.text('Header'), findsOneWidget); |
| expect(find.text('Body'), findsNothing); |
| |
| // Use the controller to toggle the expansion. |
| controller.expand(); |
| await tester.pumpAndSettle(); |
| expect(find.text('Header'), findsOneWidget); |
| expect(find.text('Body'), findsNothing); |
| |
| controller.collapse(); |
| await tester.pumpAndSettle(); |
| expect(find.text('Header'), findsOneWidget); |
| expect(find.text('Body'), findsNothing); |
| |
| controller.dispose(); |
| }); |
| |
| testWidgets('Respects maintainState', (WidgetTester tester) async { |
| final ExpansibleController controller1 = ExpansibleController(); |
| final ExpansibleController controller2 = ExpansibleController(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: SingleChildScrollView( |
| child: Column( |
| children: <Widget>[ |
| Expansible( |
| controller: controller1, |
| maintainState: false, |
| bodyBuilder: |
| (BuildContext context, Animation<double> animation) => |
| const Text('Maintaining State'), |
| headerBuilder: |
| (BuildContext context, Animation<double> animation) => GestureDetector( |
| onTap: controller1.isExpanded ? controller1.collapse : controller1.expand, |
| child: const Text('Header'), |
| ), |
| ), |
| Expansible( |
| controller: controller2, |
| bodyBuilder: |
| (BuildContext context, Animation<double> animation) => |
| const Text('Discarding State'), |
| headerBuilder: |
| (BuildContext context, Animation<double> animation) => GestureDetector( |
| onTap: controller2.isExpanded ? controller2.collapse : controller2.expand, |
| child: const Text('Header'), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ), |
| ); |
| |
| // This text is not offstage while the expansible widget is collapsed. |
| expect(find.text('Maintaining State', skipOffstage: false), findsNothing); |
| expect(find.text('Maintaining State'), findsNothing); |
| // This text is not displayed while the expansible widget is collapsed. |
| expect(find.text('Discarding State'), findsNothing); |
| |
| controller1.dispose(); |
| controller2.dispose(); |
| }); |
| |
| testWidgets('Respects animation duration and curves', (WidgetTester tester) async { |
| final ExpansibleController controller = ExpansibleController(); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Expansible( |
| controller: controller, |
| duration: const Duration(milliseconds: 120), |
| curve: Curves.easeOut, |
| reverseCurve: Curves.easeIn, |
| bodyBuilder: |
| (BuildContext context, Animation<double> animation) => |
| const SizedBox(height: 50.0, child: Placeholder()), |
| headerBuilder: |
| (BuildContext context, Animation<double> animation) => GestureDetector( |
| onTap: controller.isExpanded ? controller.collapse : controller.expand, |
| child: const Text('Header'), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(find.byType(Placeholder), findsNothing); |
| |
| await tester.tap(find.text('Header')); |
| |
| // Check that the curve is respected. |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 60)); |
| expect(tester.getBottomLeft(find.byType(Placeholder)).dy, 90.08984375); |
| |
| // The animation has completed. |
| await tester.pump(const Duration(milliseconds: 60) + const Duration(microseconds: 1)); |
| expect(tester.getBottomLeft(find.byType(Placeholder)).dy, 98.0); |
| |
| // Since the animation has completed, the vertical position doesn't change. |
| await tester.pump(const Duration(milliseconds: 60)); |
| expect(tester.getBottomLeft(find.byType(Placeholder)).dy, 98.0); |
| |
| await tester.pumpAndSettle(); |
| await tester.tap(find.text('Header')); |
| |
| // Check that the reverse curve is respected. |
| await tester.pump(); |
| await tester.pump(const Duration(milliseconds: 60)); |
| expect(tester.getBottomLeft(find.byType(Placeholder)).dy, 80.91015625); |
| |
| // The animation has completed. |
| await tester.pump(const Duration(milliseconds: 60) + const Duration(microseconds: 1)); |
| expect(find.byType(Placeholder), findsNothing); |
| |
| controller.dispose(); |
| }); |
| } |