// 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 'dart:math'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { group('Basic floating action button locations', () { testWidgets('still animates motion when the floating action button is null', (WidgetTester tester) async { await tester.pumpWidget(_buildFrame(fab: null)); expect(find.byType(FloatingActionButton), findsNothing); expect(tester.binding.transientCallbackCount, 0); await tester.pumpWidget(_buildFrame(fab: null, location: FloatingActionButtonLocation.endFloat)); expect(find.byType(FloatingActionButton), findsNothing); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpWidget(_buildFrame(fab: null, location: FloatingActionButtonLocation.centerFloat)); expect(find.byType(FloatingActionButton), findsNothing); expect(tester.binding.transientCallbackCount, greaterThan(0)); }); testWidgets('moves fab from center to end and back', (WidgetTester tester) async { await tester.pumpWidget(_buildFrame(location: FloatingActionButtonLocation.endFloat)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 356.0)); expect(tester.binding.transientCallbackCount, 0); await tester.pumpWidget(_buildFrame(location: FloatingActionButtonLocation.centerFloat)); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpAndSettle(); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 356.0)); expect(tester.binding.transientCallbackCount, 0); await tester.pumpWidget(_buildFrame(location: FloatingActionButtonLocation.endFloat)); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpAndSettle(); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 356.0)); expect(tester.binding.transientCallbackCount, 0); }); testWidgets('moves to and from custom-defined positions', (WidgetTester tester) async { await tester.pumpWidget(_buildFrame(location: const _StartTopFloatingActionButtonLocation())); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(44.0, 56.0)); await tester.pumpWidget(_buildFrame(location: FloatingActionButtonLocation.centerFloat)); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpAndSettle(); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 356.0)); expect(tester.binding.transientCallbackCount, 0); await tester.pumpWidget(_buildFrame(location: const _StartTopFloatingActionButtonLocation())); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpAndSettle(); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(44.0, 56.0)); expect(tester.binding.transientCallbackCount, 0); }); group('interrupts in-progress animations without jumps', () { _GeometryListener? geometryListener; ScaffoldGeometry? geometry; _GeometryListenerState? listenerState; Size? previousRect; Iterable<double>? previousRotations; // The maximum amounts we expect the fab width and height to change // during one step of a transition. const double maxDeltaWidth = 12.5; const double maxDeltaHeight = 12.5; // The maximum amounts we expect the fab icon to rotate during one step // of a transition. const double maxDeltaRotation = 0.09; // We'll listen to the Scaffold's geometry for any 'jumps' to detect // changes in the size and rotation of the fab. void setupListener(WidgetTester tester) { // Measure the delta in width and height of the fab, and check that it never grows // by more than the expected maximum deltas. void check() { geometry = listenerState?.cache.value; final Size? currentRect = geometry?.floatingActionButtonArea?.size; // Measure the delta in width and height of the rect, and check that // it never grows by more than a safe amount. if (previousRect != null && currentRect != null) { final double deltaWidth = currentRect.width - previousRect!.width; final double deltaHeight = currentRect.height - previousRect!.height; expect( deltaWidth.abs(), lessThanOrEqualTo(maxDeltaWidth), reason: "The Floating Action Button's width should not change " 'faster than $maxDeltaWidth per animation step.\n' 'Previous rect: $previousRect, current rect: $currentRect', ); expect( deltaHeight.abs(), lessThanOrEqualTo(maxDeltaHeight), reason: "The Floating Action Button's width should not change " 'faster than $maxDeltaHeight per animation step.\n' 'Previous rect: $previousRect, current rect: $currentRect', ); } previousRect = currentRect; // Measure the delta in rotation. // Check that it never grows by more than a safe amount. // // Note that there may be multiple transitions all active at // the same time. We are concerned only with the closest one. final Iterable<RotationTransition> rotationTransitions = tester.widgetList( find.byType(RotationTransition), ); final Iterable<double> currentRotations = rotationTransitions.map((RotationTransition t) => t.turns.value); if (previousRotations != null && previousRotations!.isNotEmpty && currentRotations != null && currentRotations.isNotEmpty && previousRect != null && currentRect != null) { final List<double> deltas = <double>[]; for (final double currentRotation in currentRotations) { late double minDelta; for (final double previousRotation in previousRotations!) { final double delta = (previousRotation - currentRotation).abs(); minDelta = delta; minDelta = min(delta, minDelta); } deltas.add(minDelta); } if (deltas.where((double delta) => delta < maxDeltaRotation).isEmpty) { fail( "The Floating Action Button's rotation should not change " 'faster than $maxDeltaRotation per animation step.\n' 'Detected deltas were: $deltas\n' 'Previous values: $previousRotations, current values: $currentRotations\n' 'Previous rect: $previousRect, current rect: $currentRect', ); } } previousRotations = currentRotations; } listenerState = tester.state(find.byType(_GeometryListener)); listenerState!.geometryListenable!.addListener(check); } setUp(() { // We create the geometry listener here, but it can only be set up // after it is pumped into the widget tree and a tester is // available. geometryListener = const _GeometryListener(); geometry = null; listenerState = null; previousRect = null; previousRotations = null; }); testWidgets('moving the fab to centerFloat', (WidgetTester tester) async { // Create a scaffold with the fab at endFloat await tester.pumpWidget(_buildFrame(location: FloatingActionButtonLocation.endFloat, listener: geometryListener)); setupListener(tester); // Move the fab to centerFloat' await tester.pumpWidget(_buildFrame(location: FloatingActionButtonLocation.centerFloat, listener: geometryListener)); await tester.pumpAndSettle(); }); testWidgets('interrupting motion towards the StartTop location.', (WidgetTester tester) async { await tester.pumpWidget(_buildFrame(location: FloatingActionButtonLocation.centerFloat, listener: geometryListener)); setupListener(tester); // Move the fab to the top start after creating the fab. await tester.pumpWidget(_buildFrame(location: const _StartTopFloatingActionButtonLocation(), listener: geometryListener)); await tester.pump(kFloatingActionButtonSegue ~/ 2); // Interrupt motion to move to the end float await tester.pumpWidget(_buildFrame(location: FloatingActionButtonLocation.endFloat, listener: geometryListener)); await tester.pumpAndSettle(); }); testWidgets('interrupting entrance to remove the fab.', (WidgetTester tester) async { await tester.pumpWidget(_buildFrame(fab: null, location: FloatingActionButtonLocation.centerFloat, listener: geometryListener)); setupListener(tester); // Animate the fab in. await tester.pumpWidget(_buildFrame(location: FloatingActionButtonLocation.endFloat, listener: geometryListener)); await tester.pump(kFloatingActionButtonSegue ~/ 2); // Remove the fab. await tester.pumpWidget( _buildFrame( fab: null, location: FloatingActionButtonLocation.endFloat, listener: geometryListener, ), ); await tester.pumpAndSettle(); }); testWidgets('interrupting entrance of a new fab.', (WidgetTester tester) async { await tester.pumpWidget( _buildFrame( fab: null, location: FloatingActionButtonLocation.endFloat, listener: geometryListener, ), ); setupListener(tester); // Bring in a new fab. await tester.pumpWidget(_buildFrame(location: FloatingActionButtonLocation.centerFloat, listener: geometryListener)); await tester.pump(kFloatingActionButtonSegue ~/ 2); // Interrupt motion to move the fab. await tester.pumpWidget( _buildFrame( location: FloatingActionButtonLocation.endFloat, listener: geometryListener, ), ); await tester.pumpAndSettle(); }); }); }); testWidgets('Docked floating action button locations', (WidgetTester tester) async { await tester.pumpWidget( _buildFrame( location: FloatingActionButtonLocation.endDocked, bab: const SizedBox(height: 100.0), viewInsets: EdgeInsets.zero, ), ); // Scaffold 800x600, FAB is 56x56, BAB is 800x100, FAB's center is // at the top of the BAB. expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 500.0)); await tester.pumpWidget( _buildFrame( location: FloatingActionButtonLocation.centerDocked, bab: const SizedBox(height: 100.0), viewInsets: EdgeInsets.zero, ), ); await tester.pumpAndSettle(); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(400.0, 500.0)); await tester.pumpWidget( _buildFrame( location: FloatingActionButtonLocation.endDocked, bab: const SizedBox(height: 100.0), viewInsets: EdgeInsets.zero, ), ); await tester.pumpAndSettle(); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 500.0)); }); testWidgets('Docked floating action button locations: no BAB, small BAB', (WidgetTester tester) async { await tester.pumpWidget( _buildFrame( location: FloatingActionButtonLocation.endDocked, viewInsets: EdgeInsets.zero, ), ); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0)); await tester.pumpWidget( _buildFrame( location: FloatingActionButtonLocation.endDocked, bab: const SizedBox(height: 16.0), viewInsets: EdgeInsets.zero, ), ); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(756.0, 572.0)); }); testWidgets('Mini-start-top floating action button location', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( appBar: AppBar(), floatingActionButton: FloatingActionButton(onPressed: () { }, mini: true), floatingActionButtonLocation: FloatingActionButtonLocation.miniStartTop, body: Column( children: const <Widget>[ ListTile( leading: CircleAvatar(), ), ], ), ), ), ); expect(tester.getCenter(find.byType(FloatingActionButton)).dx, tester.getCenter(find.byType(CircleAvatar)).dx); expect(tester.getCenter(find.byType(FloatingActionButton)).dy, kToolbarHeight); }); testWidgets('Start-top floating action button location LTR', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( appBar: AppBar(), floatingActionButton: const FloatingActionButton(onPressed: null), floatingActionButtonLocation: FloatingActionButtonLocation.startTop, ), ), ); expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(16.0, 28.0, 56.0, 56.0))); }); testWidgets('End-top floating action button location RTL', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Directionality( textDirection: TextDirection.rtl, child: Scaffold( appBar: AppBar(), floatingActionButton: const FloatingActionButton(onPressed: null), floatingActionButtonLocation: FloatingActionButtonLocation.endTop, ), ), ), ); expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(16.0, 28.0, 56.0, 56.0))); }); testWidgets('Start-top floating action button location RTL', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Directionality( textDirection: TextDirection.rtl, child: Scaffold( appBar: AppBar(), floatingActionButton: const FloatingActionButton(onPressed: null), floatingActionButtonLocation: FloatingActionButtonLocation.startTop, ), ), ), ); expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(800.0 - 56.0 - 16.0, 28.0, 56.0, 56.0))); }); testWidgets('End-top floating action button location LTR', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: Scaffold( appBar: AppBar(), floatingActionButton: const FloatingActionButton(onPressed: null), floatingActionButtonLocation: FloatingActionButtonLocation.endTop, ), ), ); expect(tester.getRect(find.byType(FloatingActionButton)), rectMoreOrLessEquals(const Rect.fromLTWH(800.0 - 56.0 - 16.0, 28.0, 56.0, 56.0))); }); group('New Floating Action Button Locations', () { testWidgets('startTop', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startTop)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _topOffsetY)); }); testWidgets('centerTop', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerTop)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _topOffsetY)); }); testWidgets('endTop', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endTop)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _topOffsetY)); }); testWidgets('startFloat', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startFloat)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _floatOffsetY)); }); testWidgets('centerFloat', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerFloat)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _floatOffsetY)); }); testWidgets('endFloat', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endFloat)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _floatOffsetY)); }); testWidgets('startDocked', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startDocked)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _dockedOffsetY)); }); testWidgets('centerDocked', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerDocked)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _dockedOffsetY)); }); testWidgets('endDocked', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endDocked)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _dockedOffsetY)); }); testWidgets('miniStartTop', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniStartTop)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniLeftOffsetX, _topOffsetY)); }); testWidgets('miniEndTop', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniEndTop)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniRightOffsetX, _topOffsetY)); }); testWidgets('miniStartFloat', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniStartFloat)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniLeftOffsetX, _miniFloatOffsetY)); }); testWidgets('miniCenterFloat', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniCenterFloat)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _miniFloatOffsetY)); }); testWidgets('miniEndFloat', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniEndFloat)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniRightOffsetX, _miniFloatOffsetY)); }); testWidgets('miniStartDocked', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniStartDocked)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniLeftOffsetX, _dockedOffsetY)); }); testWidgets('miniEndDocked', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniEndDocked)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniRightOffsetX, _dockedOffsetY)); }); // Test a few RTL cases. testWidgets('endTop, RTL', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endTop, textDirection: TextDirection.rtl)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _topOffsetY)); }); testWidgets('miniStartFloat, RTL', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.miniStartFloat, textDirection: TextDirection.rtl)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_miniRightOffsetX, _miniFloatOffsetY)); }); }); group('Custom Floating Action Button Locations', () { testWidgets('Almost end float', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(_AlmostEndFloatFabLocation())); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX - 50, _floatOffsetY)); }); testWidgets('Almost end float, RTL', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(_AlmostEndFloatFabLocation(), textDirection: TextDirection.rtl)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX + 50, _floatOffsetY)); }); testWidgets('Quarter end top', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(_QuarterEndTopFabLocation())); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX * 0.75 + _leftOffsetX * 0.25, _topOffsetY)); }); testWidgets('Quarter end top, RTL', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(_QuarterEndTopFabLocation(), textDirection: TextDirection.rtl)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX * 0.75 + _rightOffsetX * 0.25, _topOffsetY)); }); }); group('Moves involving new locations', () { testWidgets('Moves between new locations and new locations', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerTop)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _topOffsetY)); await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startFloat)); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpAndSettle(); expect(tester.binding.transientCallbackCount, 0); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _floatOffsetY)); await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startDocked)); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpAndSettle(); expect(tester.binding.transientCallbackCount, 0); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _dockedOffsetY)); }); testWidgets('Moves between new locations and old locations', (WidgetTester tester) async { await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.endDocked)); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_rightOffsetX, _dockedOffsetY)); await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.startDocked)); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpAndSettle(); expect(tester.binding.transientCallbackCount, 0); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_leftOffsetX, _dockedOffsetY)); await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerFloat)); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpAndSettle(); expect(tester.binding.transientCallbackCount, 0); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _floatOffsetY)); await tester.pumpWidget(_singleFabScaffold(FloatingActionButtonLocation.centerTop)); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pumpAndSettle(); expect(tester.binding.transientCallbackCount, 0); expect(tester.getCenter(find.byType(FloatingActionButton)), const Offset(_centerOffsetX, _topOffsetY)); }); testWidgets('Moves between new locations and old locations with custom animator', (WidgetTester tester) async { final FloatingActionButtonAnimator animator = _LinearMovementFabAnimator(); const Offset begin = Offset(_centerOffsetX, _topOffsetY); const Offset end = Offset(_rightOffsetX - 50, _floatOffsetY); final Duration animationDuration = kFloatingActionButtonSegue * 2; await tester.pumpWidget(_singleFabScaffold( FloatingActionButtonLocation.centerTop, animator: animator, )); expect(find.byType(FloatingActionButton), findsOneWidget); expect(tester.binding.transientCallbackCount, 0); await tester.pumpWidget(_singleFabScaffold( _AlmostEndFloatFabLocation(), animator: animator, )); expect(tester.binding.transientCallbackCount, greaterThan(0)); await tester.pump(animationDuration * 0.25); expect(tester.getCenter(find.byType(FloatingActionButton)), offsetMoreOrLessEquals(begin * 0.75 + end * 0.25)); await tester.pump(animationDuration * 0.25); expect(tester.getCenter(find.byType(FloatingActionButton)), offsetMoreOrLessEquals(begin * 0.5 + end * 0.5)); await tester.pump(animationDuration * 0.25); expect(tester.getCenter(find.byType(FloatingActionButton)), offsetMoreOrLessEquals(begin * 0.25 + end * 0.75)); await tester.pumpAndSettle(); expect(tester.getCenter(find.byType(FloatingActionButton)), end); expect(tester.binding.transientCallbackCount, 0); }); }); group('Locations account for safe interactive areas', () { Widget buildTest( FloatingActionButtonLocation location, MediaQueryData data, Key key, { bool mini = false, bool appBar = false, bool bottomNavigationBar = false, bool bottomSheet = false, bool resizeToAvoidBottomInset = true, }) { return MaterialApp( home: MediaQuery( data: data, child: Scaffold( resizeToAvoidBottomInset: resizeToAvoidBottomInset, bottomSheet: bottomSheet ? const SizedBox( height: 100, child: Center(child: Text('BottomSheet')), ) : null, appBar: appBar ? AppBar(title: const Text('Demo')) : null, bottomNavigationBar: bottomNavigationBar ? BottomNavigationBar( items: const <BottomNavigationBarItem>[ BottomNavigationBarItem( icon: Icon(Icons.star), label: '0', ), BottomNavigationBarItem( icon: Icon(Icons.star_border), label: '1', ), ], ) : null, floatingActionButtonLocation: location, floatingActionButton: Builder( builder: (BuildContext context) { return FloatingActionButton( onPressed: () { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Snacky!')), ); }, mini: mini, key: key, child: const Text('FAB'), ); }, ), ), ), ); } // Test float locations, for each (6), keyboard presented or not: // - Default // - with resizeToAvoidBottomInset: false // - with BottomNavigationBar // - with BottomNavigationBar and resizeToAvoidBottomInset: false // - with BottomNavigationBar & BottomSheet // - with BottomNavigationBar & BottomSheet, resizeToAvoidBottomInset: false // - with BottomSheet // - with BottomSheet and resizeToAvoidBottomInset: false // - with SnackBar Future<void> runFloatTests( WidgetTester tester, FloatingActionButtonLocation location, { required Rect defaultRect, required Rect bottomNavigationBarRect, required Rect bottomSheetRect, required Rect snackBarRect, bool mini = false, }) async { const double keyboardHeight = 200.0; const double viewPadding = 50.0; final Key floatingActionButton = UniqueKey(); const double bottomNavHeight = 106.0; // Default await tester.pumpWidget(buildTest( location, const MediaQueryData(viewPadding: EdgeInsets.only(bottom: viewPadding)), floatingActionButton, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(defaultRect), ); // Present keyboard and check position, should change await tester.pumpWidget(buildTest( location, const MediaQueryData( viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(defaultRect.translate( 0.0, viewPadding - keyboardHeight, )), ); // With resizeToAvoidBottomInset: false // With keyboard presented, should maintain default position await tester.pumpWidget(buildTest( location, const MediaQueryData( viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, resizeToAvoidBottomInset: false, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(defaultRect), ); // BottomNavigationBar default await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), ), floatingActionButton, bottomNavigationBar: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomNavigationBarRect), ); // Present keyboard and check position, FAB position changes await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, bottomNavigationBar: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomNavigationBarRect.translate( 0.0, -keyboardHeight + bottomNavHeight, )), ); // BottomNavigationBar with resizeToAvoidBottomInset: false // With keyboard presented, should maintain default position await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, bottomNavigationBar: true, resizeToAvoidBottomInset: false, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomNavigationBarRect), ); // BottomNavigationBar + BottomSheet default await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), ), floatingActionButton, bottomNavigationBar: true, bottomSheet: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomSheetRect.translate( 0.0, -bottomNavHeight, )), ); // Present keyboard and check position, FAB position changes await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, bottomNavigationBar: true, bottomSheet: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomSheetRect.translate( 0.0, -keyboardHeight, )), ); // BottomNavigationBar + BottomSheet with resizeToAvoidBottomInset: false // With keyboard presented, should maintain default position await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, bottomNavigationBar: true, bottomSheet: true, resizeToAvoidBottomInset: false, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomSheetRect.translate( 0.0, -bottomNavHeight, )), ); // BottomSheet default await tester.pumpWidget(buildTest( location, const MediaQueryData(viewPadding: EdgeInsets.only(bottom: viewPadding)), floatingActionButton, bottomSheet: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomSheetRect), ); // Present keyboard and check position, bottomSheet and FAB both resize await tester.pumpWidget(buildTest( location, const MediaQueryData( viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, bottomSheet: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomSheetRect.translate(0.0, -keyboardHeight)), ); // bottomSheet with resizeToAvoidBottomInset: false // With keyboard presented, should maintain default bottomSheet position await tester.pumpWidget(buildTest( location, const MediaQueryData( viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, bottomSheet: true, resizeToAvoidBottomInset: false, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomSheetRect), ); // SnackBar default await tester.pumpWidget(buildTest( location, const MediaQueryData(viewPadding: EdgeInsets.only(bottom: viewPadding)), floatingActionButton, mini: mini, )); await tester.tap(find.byKey(floatingActionButton)); await tester.pumpAndSettle(); // Show SnackBar expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(snackBarRect), ); // SnackBar when resized for presented keyboard await tester.pumpWidget(buildTest( location, const MediaQueryData( viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, mini: mini, )); await tester.tap(find.byKey(floatingActionButton)); await tester.pumpAndSettle(); // Show SnackBar expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(snackBarRect.translate(0.0, -keyboardHeight + kFloatingActionButtonMargin/2)), ); } testWidgets('startFloat', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(16.0, 478.0, 72.0, 534.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(16.0, 422.0, 72.0, 478.0); // Position relative to BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(16.0, 472.0, 72.0, 528.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(16.0, 478.0, 72.0, 534.0); await runFloatTests( tester, FloatingActionButtonLocation.startFloat, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, ); }); testWidgets('miniStartFloat', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(12.0, 490.0, 60.0, 538.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(12.0, 434.0, 60.0, 482.0); // Positioned relative to BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(12.0, 480.0, 60.0, 528.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(12.0, 490.0, 60.0, 538.0); await runFloatTests( tester, FloatingActionButtonLocation.miniStartFloat, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, mini: true, ); }); testWidgets('centerFloat', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(372.0, 478.0, 428.0, 534.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(372.0, 422.0, 428.0, 478.0); // Positioned relative to BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(372.0, 472.0, 428.0, 528.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(372.0, 478.0, 428.0, 534.0); await runFloatTests( tester, FloatingActionButtonLocation.centerFloat, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, ); }); testWidgets('miniCenterFloat', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(376.0, 490.0, 424.0, 538.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(376.0, 434.0, 424.0, 482.0); // Positioned relative to BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(376.0, 480.0, 424.0, 528.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(376.0, 490.0, 424.0, 538.0); await runFloatTests( tester, FloatingActionButtonLocation.miniCenterFloat, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, mini: true, ); }); testWidgets('endFloat', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(728.0, 478.0, 784.0, 534.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(728.0, 422.0, 784.0, 478.0); // Positioned relative to BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(728.0, 472.0, 784.0, 528.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(728.0, 478.0, 784.0, 534.0); await runFloatTests( tester, FloatingActionButtonLocation.endFloat, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, ); }); testWidgets('miniEndFloat', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(740.0, 490.0, 788.0, 538.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(740.0, 434.0, 788.0, 482.0); // Positioned relative to BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(740.0, 480.0, 788.0, 528.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(740.0, 490.0, 788.0, 538.0); await runFloatTests( tester, FloatingActionButtonLocation.miniEndFloat, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, mini: true, ); }); // Test docked locations, for each (6), keyboard presented or not. // If keyboard is presented and resizeToAvoidBottomInset: true, test whether // the FAB is away from the keyboard(and thus not clipped): // - Default // - Default with resizeToAvoidBottomInset: false // - docked with BottomNavigationBar // - docked with BottomNavigationBar and resizeToAvoidBottomInset: false // - docked with BottomNavigationBar & BottomSheet // - docked with BottomNavigationBar & BottomSheet, resizeToAvoidBottomInset: false // - with SnackBar Future<void> runDockedTests( WidgetTester tester, FloatingActionButtonLocation location, { required Rect defaultRect, required Rect bottomNavigationBarRect, required Rect bottomSheetRect, required Rect snackBarRect, bool mini = false, }) async { const double keyboardHeight = 200.0; const double viewPadding = 50.0; const double bottomNavHeight = 106.0; const double scaffoldHeight = 600.0; final Key floatingActionButton = UniqueKey(); final double fabHeight = mini ? 48.0 : 56.0; // Default await tester.pumpWidget(buildTest( location, const MediaQueryData(viewPadding: EdgeInsets.only(bottom: viewPadding)), floatingActionButton, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(defaultRect), ); // Present keyboard and check position, should change await tester.pumpWidget(buildTest( location, const MediaQueryData( viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(defaultRect.translate( 0.0, viewPadding - keyboardHeight - kFloatingActionButtonMargin, )), ); // The FAB should be away from the keyboard expect( tester.getRect(find.byKey(floatingActionButton)).bottom, lessThan(scaffoldHeight - keyboardHeight), ); // With resizeToAvoidBottomInset: false // With keyboard presented, should maintain default position await tester.pumpWidget(buildTest( location, const MediaQueryData( viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, resizeToAvoidBottomInset: false, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(defaultRect), ); // BottomNavigationBar default await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), ), floatingActionButton, bottomNavigationBar: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomNavigationBarRect), ); // Present keyboard and check position, FAB position changes await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, bottomNavigationBar: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomNavigationBarRect.translate( 0.0, bottomNavHeight + fabHeight / 2.0 - keyboardHeight - kFloatingActionButtonMargin - fabHeight, )), ); // The FAB should be away from the keyboard expect( tester.getRect(find.byKey(floatingActionButton)).bottom, lessThan(scaffoldHeight - keyboardHeight), ); // BottomNavigationBar with resizeToAvoidBottomInset: false // With keyboard presented, should maintain default position await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, bottomNavigationBar: true, resizeToAvoidBottomInset: false, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomNavigationBarRect), ); // BottomNavigationBar + BottomSheet default await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), ), floatingActionButton, bottomNavigationBar: true, bottomSheet: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomSheetRect), ); // Present keyboard and check position, FAB position changes await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, bottomNavigationBar: true, bottomSheet: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomSheetRect.translate( 0.0, -keyboardHeight + bottomNavHeight, )), ); // The FAB should be away from the keyboard expect( tester.getRect(find.byKey(floatingActionButton)).bottom, lessThan(scaffoldHeight - keyboardHeight), ); // BottomNavigationBar + BottomSheet with resizeToAvoidBottomInset: false // With keyboard presented, should maintain default position await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, bottomNavigationBar: true, bottomSheet: true, resizeToAvoidBottomInset: false, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(bottomSheetRect), ); // SnackBar default await tester.pumpWidget(buildTest( location, const MediaQueryData(viewPadding: EdgeInsets.only(bottom: viewPadding)), floatingActionButton, mini: mini, )); await tester.tap(find.byKey(floatingActionButton)); await tester.pumpAndSettle(); // Show SnackBar expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(snackBarRect), ); // SnackBar with BottomNavigationBar await tester.pumpWidget(buildTest( location, const MediaQueryData( padding: EdgeInsets.only(bottom: viewPadding), viewPadding: EdgeInsets.only(bottom: viewPadding), ), floatingActionButton, bottomNavigationBar: true, mini: mini, )); await tester.tap(find.byKey(floatingActionButton)); await tester.pumpAndSettle(); // Show SnackBar expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(snackBarRect.translate(0.0, -bottomNavHeight)), ); // SnackBar when resized for presented keyboard await tester.pumpWidget(buildTest( location, const MediaQueryData( viewPadding: EdgeInsets.only(bottom: viewPadding), viewInsets: EdgeInsets.only(bottom: keyboardHeight), ), floatingActionButton, mini: mini, )); await tester.tap(find.byKey(floatingActionButton)); await tester.pumpAndSettle(); // Show SnackBar expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(snackBarRect.translate(0.0, -keyboardHeight)), ); // The FAB should be away from the keyboard expect( tester.getRect(find.byKey(floatingActionButton)).bottom, lessThan(scaffoldHeight - keyboardHeight), ); } testWidgets('startDocked', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(16.0, 494.0, 72.0, 550.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(16.0, 466.0, 72.0, 522.0); // Positioned relative to BottomNavigationBar & BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(16.0, 366.0, 72.0, 422.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(16.0, 486.0, 72.0, 542.0); await runDockedTests( tester, FloatingActionButtonLocation.startDocked, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, ); }); testWidgets('miniStartDocked', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(12.0, 502.0, 60.0, 550.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(12.0, 470.0, 60.0, 518.0); // Positioned relative to BottomNavigationBar & BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(12.0, 370.0, 60.0, 418.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(12.0, 494.0, 60.0, 542.0); await runDockedTests( tester, FloatingActionButtonLocation.miniStartDocked, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, mini: true, ); }); testWidgets('centerDocked', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(372.0, 494.0, 428.0, 550.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(372.0, 466.0, 428.0, 522.0); // Positioned relative to BottomNavigationBar & BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(372.0, 366.0, 428.0, 422.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(372.0, 486.0, 428.0, 542.0); await runDockedTests( tester, FloatingActionButtonLocation.centerDocked, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, ); }); testWidgets('miniCenterDocked', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(376.0, 502.0, 424.0, 550.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(376.0, 470.0, 424.0, 518.0); // Positioned relative to BottomNavigationBar & BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(376.0, 370.0, 424.0, 418.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(376.0, 494.0, 424.0, 542.0); await runDockedTests( tester, FloatingActionButtonLocation.miniCenterDocked, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, mini: true, ); }); testWidgets('endDocked', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(728.0, 494.0, 784.0, 550.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(728.0, 466.0, 784.0, 522.0); // Positioned relative to BottomNavigationBar & BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(728.0, 366.0, 784.0, 422.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(728.0, 486.0, 784.0, 542.0); await runDockedTests( tester, FloatingActionButtonLocation.endDocked, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, ); }); testWidgets('miniEndDocked', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(740.0, 502.0, 788.0, 550.0); // Positioned relative to BottomNavigationBar const Rect bottomNavigationBarRect = Rect.fromLTRB(740.0, 470.0, 788.0, 518.0); // Positioned relative to BottomNavigationBar & BottomSheet const Rect bottomSheetRect = Rect.fromLTRB(740.0, 370.0, 788.0, 418.0); // Positioned relative to SnackBar const Rect snackBarRect = Rect.fromLTRB(740.0, 494.0, 788.0, 542.0); await runDockedTests( tester, FloatingActionButtonLocation.miniEndDocked, defaultRect: defaultRect, bottomNavigationBarRect: bottomNavigationBarRect, bottomSheetRect: bottomSheetRect, snackBarRect: snackBarRect, mini: true, ); }); // Test top locations, for each (6): // - Default // - with an AppBar Future<void> runTopTests( WidgetTester tester, FloatingActionButtonLocation location, { required Rect defaultRect, required Rect appBarRect, bool mini = false, }) async { const double viewPadding = 50.0; final Key floatingActionButton = UniqueKey(); // Default await tester.pumpWidget(buildTest( location, const MediaQueryData(viewPadding: EdgeInsets.only(top: viewPadding)), floatingActionButton, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(defaultRect), ); // AppBar default await tester.pumpWidget(buildTest( location, const MediaQueryData(viewPadding: EdgeInsets.only(top: viewPadding)), floatingActionButton, appBar: true, mini: mini, )); expect( tester.getRect(find.byKey(floatingActionButton)), rectMoreOrLessEquals(appBarRect), ); } testWidgets('startTop', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(16.0, 50.0, 72.0, 106.0); // Positioned relative to AppBar const Rect appBarRect = Rect.fromLTRB(16.0, 28.0, 72.0, 84.0); await runTopTests( tester, FloatingActionButtonLocation.startTop, defaultRect: defaultRect, appBarRect: appBarRect, ); }); testWidgets('miniStartTop', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(12.0, 50.0, 60.0, 98.0); // Positioned relative to AppBar const Rect appBarRect = Rect.fromLTRB(12.0, 32.0, 60.0, 80.0); await runTopTests( tester, FloatingActionButtonLocation.miniStartTop, defaultRect: defaultRect, appBarRect: appBarRect, mini: true, ); }); testWidgets('centerTop', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(372.0, 50.0, 428.0, 106.0); // Positioned relative to AppBar const Rect appBarRect = Rect.fromLTRB(372.0, 28.0, 428.0, 84.0); await runTopTests( tester, FloatingActionButtonLocation.centerTop, defaultRect: defaultRect, appBarRect: appBarRect, ); }); testWidgets('miniCenterTop', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(376.0, 50.0, 424.0, 98.0); // Positioned relative to AppBar const Rect appBarRect = Rect.fromLTRB(376.0, 32.0, 424.0, 80.0); await runTopTests( tester, FloatingActionButtonLocation.miniCenterTop, defaultRect: defaultRect, appBarRect: appBarRect, mini: true, ); }); testWidgets('endTop', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(728.0, 50.0, 784.0, 106.0); // Positioned relative to AppBar const Rect appBarRect = Rect.fromLTRB(728.0, 28.0, 784.0, 84.0); await runTopTests( tester, FloatingActionButtonLocation.endTop, defaultRect: defaultRect, appBarRect: appBarRect, ); }); testWidgets('miniEndTop', (WidgetTester tester) async { const Rect defaultRect = Rect.fromLTRB(740.0, 50.0, 788.0, 98.0); // Positioned relative to AppBar const Rect appBarRect = Rect.fromLTRB(740.0, 32.0, 788.0, 80.0); await runTopTests( tester, FloatingActionButtonLocation.miniEndTop, defaultRect: defaultRect, appBarRect: appBarRect, mini: true, ); }); }); } class _GeometryListener extends StatefulWidget { const _GeometryListener(); @override State createState() => _GeometryListenerState(); } class _GeometryListenerState extends State<_GeometryListener> { @override Widget build(BuildContext context) { return CustomPaint( painter: cache, ); } int numNotifications = 0; ValueListenable<ScaffoldGeometry>? geometryListenable; late _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; } } const double _leftOffsetX = 44.0; const double _centerOffsetX = 400.0; const double _rightOffsetX = 756.0; const double _miniLeftOffsetX = _leftOffsetX - kMiniButtonOffsetAdjustment; const double _miniRightOffsetX = _rightOffsetX + kMiniButtonOffsetAdjustment; const double _topOffsetY = 56.0; const double _floatOffsetY = 500.0; const double _dockedOffsetY = 544.0; const double _miniFloatOffsetY = _floatOffsetY + kMiniButtonOffsetAdjustment; Widget _singleFabScaffold( FloatingActionButtonLocation location, { FloatingActionButtonAnimator? animator, bool mini = false, TextDirection textDirection = TextDirection.ltr, } ) { return MaterialApp( home: Directionality( textDirection: textDirection, child: Scaffold( appBar: AppBar( title: const Text('FloatingActionButtonLocation Test.'), ), bottomNavigationBar: BottomNavigationBar( items: const <BottomNavigationBarItem>[ BottomNavigationBarItem( icon: Icon(Icons.home), label: 'Home', ), BottomNavigationBarItem( icon: Icon(Icons.school), label: 'School', ), ], ), floatingActionButton: FloatingActionButton( onPressed: () {}, mini: mini, child: const Icon(Icons.beach_access), ), floatingActionButtonLocation: location, floatingActionButtonAnimator: animator, ), ), ); } // 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; late ScaffoldGeometry value; @override void paint(Canvas canvas, Size size) { value = geometryListenable.value; } @override bool shouldRepaint(_GeometryCachePainter oldDelegate) { return true; } } Widget _buildFrame({ FloatingActionButton? fab = const FloatingActionButton( onPressed: null, child: Text('1'), ), FloatingActionButtonLocation? location, _GeometryListener? listener, TextDirection textDirection = TextDirection.ltr, EdgeInsets viewInsets = const EdgeInsets.only(bottom: 200.0), Widget? bab, }) { return Localizations( locale: const Locale('en', 'us'), delegates: const <LocalizationsDelegate<dynamic>>[ DefaultWidgetsLocalizations.delegate, DefaultMaterialLocalizations.delegate, ], child: Directionality( textDirection: textDirection, child: MediaQuery( data: MediaQueryData(viewInsets: viewInsets), child: Scaffold( appBar: AppBar(title: const Text('FabLocation Test')), floatingActionButtonLocation: location, floatingActionButton: fab, bottomNavigationBar: bab, body: listener, ), ), ), ); } class _StartTopFloatingActionButtonLocation extends FloatingActionButtonLocation { const _StartTopFloatingActionButtonLocation(); @override Offset getOffset(ScaffoldPrelayoutGeometry scaffoldGeometry) { double fabX; assert(scaffoldGeometry.textDirection != null); switch (scaffoldGeometry.textDirection) { case TextDirection.rtl: final double startPadding = kFloatingActionButtonMargin + scaffoldGeometry.minInsets.right; fabX = scaffoldGeometry.scaffoldSize.width - scaffoldGeometry.floatingActionButtonSize.width - startPadding; break; case TextDirection.ltr: final double startPadding = kFloatingActionButtonMargin + scaffoldGeometry.minInsets.left; fabX = startPadding; break; } final double fabY = scaffoldGeometry.contentTop - (scaffoldGeometry.floatingActionButtonSize.height / 2.0); return Offset(fabX, fabY); } } class _AlmostEndFloatFabLocation extends StandardFabLocation with FabEndOffsetX, FabFloatOffsetY { @override double getOffsetX (ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) { final double directionalAdjustment = scaffoldGeometry.textDirection == TextDirection.ltr ? -50.0 : 50.0; return super.getOffsetX(scaffoldGeometry, adjustment) + directionalAdjustment; } } class _QuarterEndTopFabLocation extends StandardFabLocation with FabEndOffsetX, FabTopOffsetY { @override double getOffsetX (ScaffoldPrelayoutGeometry scaffoldGeometry, double adjustment) { return super.getOffsetX(scaffoldGeometry, adjustment) * 0.75 + (FloatingActionButtonLocation.startFloat as StandardFabLocation) .getOffsetX(scaffoldGeometry, adjustment) * 0.25; } } class _LinearMovementFabAnimator extends FloatingActionButtonAnimator { @override Offset getOffset({required Offset begin, required Offset end, required double progress}) { return Offset.lerp(begin, end, progress)!; } @override Animation<double> getScaleAnimation({required Animation<double> parent}) { return const AlwaysStoppedAnimation<double>(1.0); } @override Animation<double> getRotationAnimation({required Animation<double> parent}) { return const AlwaysStoppedAnimation<double>(1.0); } }