| // 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' as math; |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| void main() { |
| |
| // Helpers |
| final Widget sliverBox = SliverToBoxAdapter( |
| child: Container( |
| color: Colors.amber, |
| height: 150.0, |
| width: 150, |
| ), |
| ); |
| Widget boilerplate( |
| List<Widget> slivers, { |
| ScrollController? controller, |
| Axis scrollDirection = Axis.vertical, |
| }) { |
| return MaterialApp( |
| theme: ThemeData( |
| materialTapTargetSize: MaterialTapTargetSize.padded, |
| ), |
| home: Scaffold( |
| body: CustomScrollView( |
| scrollDirection: scrollDirection, |
| slivers: slivers, |
| controller: controller, |
| ), |
| ), |
| ); |
| } |
| |
| group('SliverFillRemaining', () { |
| group('hasScrollBody: true, default', () { |
| testWidgets('no siblings', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: CustomScrollView( |
| controller: controller, |
| slivers: <Widget>[ |
| SliverFillRemaining(child: Container()), |
| ], |
| ), |
| ), |
| ); |
| expect( |
| tester.renderObject<RenderBox>(find.byType(Container)).size.height, |
| equals(600.0), |
| ); |
| |
| controller.jumpTo(50.0); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byType(Container)).size.height, |
| equals(600.0), |
| ); |
| |
| controller.jumpTo(-100.0); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byType(Container)).size.height, |
| equals(600.0), |
| ); |
| |
| controller.jumpTo(0.0); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byType(Container)).size.height, |
| equals(600.0), |
| ); |
| }); |
| |
| testWidgets('one sibling', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: CustomScrollView( |
| controller: controller, |
| slivers: <Widget>[ |
| const SliverToBoxAdapter(child: SizedBox(height: 100.0)), |
| SliverFillRemaining(child: Container()), |
| ], |
| ), |
| ), |
| ); |
| expect( |
| tester.renderObject<RenderBox>(find.byType(Container)).size.height, |
| equals(500.0), |
| ); |
| |
| controller.jumpTo(50.0); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byType(Container)).size.height, |
| equals(550.0), |
| ); |
| |
| controller.jumpTo(-100.0); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byType(Container)).size.height, |
| equals(400.0), |
| ); |
| |
| controller.jumpTo(0.0); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byType(Container)).size.height, |
| equals(500.0), |
| ); |
| }); |
| |
| testWidgets('scrolls beyond viewportMainAxisExtent', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| child: Container(color: Colors.white), |
| ), |
| ]; |
| await tester.pumpWidget(boilerplate(slivers, controller: controller)); |
| expect(controller.offset, 0.0); |
| expect(find.byType(Container), findsNWidgets(2)); |
| controller.jumpTo(150.0); |
| await tester.pumpAndSettle(); |
| expect(controller.offset, 150.0); |
| expect(find.byType(Container), findsOneWidget); |
| }); |
| }); |
| |
| group('hasScrollBody: false', () { |
| testWidgets('does not extend past viewportMainAxisExtent', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| hasScrollBody: false, |
| child: Container(color: Colors.white), |
| ), |
| ]; |
| |
| await tester.pumpWidget(boilerplate(slivers, controller: controller)); |
| expect(controller.offset, 0.0); |
| expect(find.byType(Container), findsNWidgets(2)); |
| controller.jumpTo(150.0); |
| await tester.pumpAndSettle(); |
| expect(controller.offset, 0.0); |
| expect(find.byType(Container), findsNWidgets(2)); |
| }); |
| |
| testWidgets('child without size is sized by extent', (WidgetTester tester) async { |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| hasScrollBody: false, |
| child: Container(color: Colors.blue), |
| ), |
| ]; |
| |
| await tester.pumpWidget(boilerplate(slivers)); |
| RenderBox box = tester.renderObject<RenderBox>(find.byType(Container).last); |
| expect(box.size.height, equals(450)); |
| |
| await tester.pumpWidget(boilerplate( |
| slivers, |
| scrollDirection: Axis.horizontal, |
| )); |
| box = tester.renderObject<RenderBox>(find.byType(Container).last); |
| expect(box.size.width, equals(650)); |
| }); |
| |
| testWidgets('child with smaller size is sized by extent', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| hasScrollBody: false, |
| child: Container( |
| key: key, |
| color: Colors.blue, |
| child: Align( |
| alignment: Alignment.bottomCenter, |
| child: ElevatedButton( |
| child: const Text('bottomCenter button'), |
| onPressed: () {}, |
| ), |
| ), |
| ), |
| ), |
| ]; |
| await tester.pumpWidget(boilerplate(slivers)); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(450), |
| ); |
| |
| // Also check that the button alignment is true to expectations |
| final Finder button = find.byType(ElevatedButton); |
| expect(tester.getBottomLeft(button).dy, equals(600.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| |
| // Check Axis.horizontal |
| await tester.pumpWidget(boilerplate( |
| slivers, |
| scrollDirection: Axis.horizontal, |
| )); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.width, |
| equals(650), |
| ); |
| }); |
| |
| testWidgets('extent is overridden by child with larger size', (WidgetTester tester) async { |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| hasScrollBody: false, |
| child: Container( |
| color: Colors.blue, |
| height: 600, |
| width: 1000, |
| ), |
| ), |
| ]; |
| await tester.pumpWidget(boilerplate(slivers)); |
| RenderBox box = tester.renderObject<RenderBox>(find.byType(Container).last); |
| expect(box.size.height, equals(600)); |
| |
| await tester.pumpWidget(boilerplate( |
| slivers, |
| scrollDirection: Axis.horizontal, |
| )); |
| box = tester.renderObject<RenderBox>(find.byType(Container).last); |
| expect(box.size.width, equals(1000)); |
| }); |
| |
| testWidgets('extent is overridden by child size if precedingScrollExtent > viewportMainAxisExtent', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final List<Widget> slivers = <Widget>[ |
| SliverFixedExtentList( |
| itemExtent: 150, |
| delegate: SliverChildBuilderDelegate( |
| (BuildContext context, int index) => Container(color: Colors.amber), |
| childCount: 5, |
| ), |
| ), |
| SliverFillRemaining( |
| hasScrollBody: false, |
| child: Container( |
| key: key, |
| color: Colors.blue[300], |
| child: Align( |
| child: Padding( |
| padding: const EdgeInsets.all(50.0), |
| child: ElevatedButton( |
| child: const Text('center button'), |
| onPressed: () {}, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ]; |
| await tester.pumpWidget(boilerplate(slivers)); |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -750.0)); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(148.0), |
| ); |
| |
| // Also check that the button alignment is true to expectations |
| final Finder button = find.byType(ElevatedButton); |
| expect(tester.getBottomLeft(button).dy, equals(550.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| }); |
| |
| testWidgets('alignment with a flexible works', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| hasScrollBody: false, |
| child: Column( |
| key: key, |
| mainAxisSize: MainAxisSize.min, |
| children: <Widget>[ |
| const Flexible( |
| child: Center(child: FlutterLogo(size: 100)), |
| ), |
| ElevatedButton( |
| child: const Text('Bottom'), |
| onPressed: () {}, |
| ), |
| ], |
| ), |
| ), |
| ]; |
| |
| await tester.pumpWidget(boilerplate(slivers)); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(450), |
| ); |
| |
| // Check that the logo alignment is true to expectations |
| final Finder logo = find.byType(FlutterLogo); |
| expect( |
| tester.renderObject<RenderBox>(logo).size, |
| const Size(100.0, 100.0), |
| ); |
| final VisualDensity density = VisualDensity.adaptivePlatformDensity; |
| expect(tester.getCenter(logo), Offset(400.0, 351.0 - density.vertical * 2.0)); |
| |
| // Also check that the button alignment is true to expectations |
| // Buttons do not decrease their horizontal padding per the VisualDensity. |
| final Finder button = find.byType(ElevatedButton); |
| expect( |
| tester.renderObject<RenderBox>(button).size, |
| Size(116.0 + math.max(0, density.horizontal) * 8.0, 48.0 + density.vertical * 4.0), |
| ); |
| expect(tester.getBottomLeft(button).dy, equals(600.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| |
| // Overscroll and see that alignment and size is maintained |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -50.0)); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(450), |
| ); |
| expect( |
| tester.renderObject<RenderBox>(logo).size, |
| const Size(100.0, 100.0), |
| ); |
| expect(tester.getCenter(logo).dy, lessThan(351.0)); |
| expect( |
| tester.renderObject<RenderBox>(button).size, |
| // Buttons do not decrease their horizontal padding per the VisualDensity. |
| Size(116.0 + math.max(0, density.horizontal) * 8.0, 48.0 + density.vertical * 4.0), |
| ); |
| expect(tester.getBottomLeft(button).dy, lessThan(600.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); |
| |
| group('fillOverscroll: true, relevant platforms', () { |
| testWidgets('child without size is sized by extent and overscroll', (WidgetTester tester) async { |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| hasScrollBody: false, |
| fillOverscroll: true, |
| child: Container(color: Colors.blue), |
| ), |
| ]; |
| |
| // Check size |
| await tester.pumpWidget(boilerplate(slivers)); |
| final RenderBox box1 = tester.renderObject<RenderBox>(find.byType(Container).last); |
| expect(box1.size.height, equals(450)); |
| |
| // Overscroll and check size |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -50.0)); |
| await tester.pump(); |
| final RenderBox box2 = tester.renderObject<RenderBox>(find.byType(Container).last); |
| expect(box2.size.height, greaterThan(450)); |
| |
| // Ensure overscroll retracts to original size after releasing gesture |
| await tester.pumpAndSettle(); |
| final RenderBox box3 = tester.renderObject<RenderBox>(find.byType(Container).last); |
| expect(box3.size.height, equals(450)); |
| }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); |
| |
| testWidgets('child with smaller size is overridden and sized by extent and overscroll', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| hasScrollBody: false, |
| fillOverscroll: true, |
| child: Container( |
| key: key, |
| color: Colors.blue, |
| child: Align( |
| alignment: Alignment.bottomCenter, |
| child: ElevatedButton( |
| child: const Text('bottomCenter button'), |
| onPressed: () {}, |
| ), |
| ), |
| ), |
| ), |
| ]; |
| await tester.pumpWidget(boilerplate(slivers)); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(450), |
| ); |
| |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -50.0)); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| greaterThan(450), |
| ); |
| |
| // Also check that the button alignment is true to expectations, even with |
| // child stretching to fill overscroll |
| final Finder button = find.byType(ElevatedButton); |
| expect(tester.getBottomLeft(button).dy, equals(600.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| |
| // Ensure overscroll retracts to original size after releasing gesture |
| await tester.pumpAndSettle(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(450), |
| ); |
| }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); |
| |
| testWidgets('extent is overridden by child size and overscroll if precedingScrollExtent > viewportMainAxisExtent', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| final List<Widget> slivers = <Widget>[ |
| SliverFixedExtentList( |
| itemExtent: 150, |
| delegate: SliverChildBuilderDelegate( |
| (BuildContext context, int index) => |
| Container(color: Colors.amber), |
| childCount: 5, |
| ), |
| ), |
| SliverFillRemaining( |
| hasScrollBody: false, |
| fillOverscroll: true, |
| child: Container( |
| key: key, |
| color: Colors.blue[300], |
| child: Align( |
| child: Padding( |
| padding: const EdgeInsets.all(50.0), |
| child: ElevatedButton( |
| child: const Text('center button'), |
| onPressed: () {}, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ]; |
| await tester.pumpWidget(boilerplate(slivers, controller: controller)); |
| |
| // Scroll to the end |
| controller.jumpTo(controller.position.maxScrollExtent); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(148.0 + VisualDensity.adaptivePlatformDensity.vertical * 4.0), |
| ); |
| // Check that the button alignment is true to expectations |
| final Finder button = find.byType(ElevatedButton); |
| expect(tester.getBottomLeft(button).dy, equals(550.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| |
| // Drag for overscroll |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -50.0)); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| greaterThan(148.0), |
| ); |
| |
| // Check that the button alignment is still centered in stretched child |
| expect(tester.getBottomLeft(button).dy, lessThan(550.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| |
| // Ensure overscroll retracts to original size after releasing gesture |
| await tester.pumpAndSettle(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(148.0 + VisualDensity.adaptivePlatformDensity.vertical * 4.0), |
| ); |
| }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); |
| |
| testWidgets('fillOverscroll works when child has no size and precedingScrollExtent > viewportMainAxisExtent', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| final List<Widget> slivers = <Widget>[ |
| SliverFixedExtentList( |
| itemExtent: 150, |
| delegate: SliverChildBuilderDelegate( |
| (BuildContext context, int index) { |
| return Semantics(label: index.toString(), child: Container(color: Colors.amber)); |
| }, |
| childCount: 5, |
| ), |
| ), |
| SliverFillRemaining( |
| hasScrollBody: false, |
| fillOverscroll: true, |
| child: Container( |
| key: key, |
| color: Colors.blue, |
| ), |
| ), |
| ]; |
| |
| await tester.pumpWidget(boilerplate(slivers, controller: controller)); |
| |
| expect(find.byKey(key), findsNothing); |
| expect( |
| find.bySemanticsLabel('4'), |
| findsNothing, |
| ); |
| |
| // Scroll to bottom |
| controller.jumpTo(controller.position.maxScrollExtent); |
| await tester.pump(); |
| |
| // Check item at the end of the list |
| expect(find.byKey(key), findsNothing); |
| expect( |
| find.bySemanticsLabel('4'), |
| findsOneWidget, |
| ); |
| |
| // Overscroll |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -50.0)); |
| await tester.pump(); |
| |
| // Check for new item at the end of the now overscrolled list |
| expect(find.byKey(key), findsOneWidget); |
| expect( |
| find.bySemanticsLabel('4'), |
| findsOneWidget, |
| ); |
| |
| // Ensure overscroll retracts to original size after releasing gesture |
| await tester.pumpAndSettle(); |
| expect(find.byKey(key), findsNothing); |
| expect( |
| find.bySemanticsLabel('4'), |
| findsOneWidget, |
| ); |
| }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); |
| |
| testWidgets('alignment with a flexible works with fillOverscroll', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| hasScrollBody: false, |
| fillOverscroll: true, |
| child: Column( |
| key: key, |
| mainAxisSize: MainAxisSize.min, |
| children: <Widget>[ |
| const Flexible( |
| child: Center(child: FlutterLogo(size: 100)), |
| ), |
| ElevatedButton( |
| child: const Text('Bottom'), |
| onPressed: () {}, |
| ), |
| ], |
| ), |
| ), |
| ]; |
| |
| await tester.pumpWidget(boilerplate(slivers)); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(450), |
| ); |
| |
| // Check that the logo alignment is true to expectations. |
| final Finder logo = find.byType(FlutterLogo); |
| expect( |
| tester.renderObject<RenderBox>(logo).size, |
| const Size(100.0, 100.0), |
| ); |
| final VisualDensity density = VisualDensity.adaptivePlatformDensity; |
| expect(tester.getCenter(logo), Offset(400.0, 351.0 - density.vertical * 2.0)); |
| |
| // Also check that the button alignment is true to expectations. |
| // Buttons do not decrease their horizontal padding per the VisualDensity. |
| final Finder button = find.byType(ElevatedButton); |
| expect( |
| tester.renderObject<RenderBox>(button).size, |
| Size(116.0 + math.max(0, density.horizontal) * 8.0, 48.0 + density.vertical * 4.0), |
| ); |
| expect(tester.getBottomLeft(button).dy, equals(600.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| |
| // Overscroll and see that logo alignment shifts to maintain center as |
| // container stretches with overscroll, button remains aligned at the |
| // bottom. |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -50.0)); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| greaterThan(450), |
| ); |
| expect( |
| tester.renderObject<RenderBox>(logo).size, |
| const Size(100.0, 100.0), |
| ); |
| expect(tester.getCenter(logo).dy, lessThan(351.0)); |
| expect( |
| tester.renderObject<RenderBox>(button).size, |
| // Buttons do not decrease their horizontal padding per the VisualDensity. |
| Size(116.0 + math.max(0, density.horizontal) * 8.0, 48.0 + density.vertical * 4.0), |
| ); |
| expect(tester.getBottomLeft(button).dy, equals(600.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| |
| // Ensure overscroll retracts to original position when gesture is |
| // released. |
| await tester.pumpAndSettle(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(450), |
| ); |
| expect( |
| tester.renderObject<RenderBox>(logo).size, |
| const Size(100.0, 100.0), |
| ); |
| expect(tester.getCenter(logo), Offset(400.0, 351.0 - density.vertical * 2.0)); |
| expect( |
| tester.renderObject<RenderBox>(button).size, |
| // Buttons do not decrease their horizontal padding per the VisualDensity. |
| Size(116.0 + math.max(0, density.horizontal) * 8.0, 48.0 + density.vertical * 4.0), |
| ); |
| expect(tester.getBottomLeft(button).dy, equals(600.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); |
| }); |
| |
| group('fillOverscroll: true, is ignored on irrelevant platforms', () { |
| // Android/Other scroll physics when hasScrollBody: false, ignores fillOverscroll: true |
| testWidgets('child without size is sized by extent', (WidgetTester tester) async { |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| hasScrollBody: false, |
| fillOverscroll: true, |
| child: Container(color: Colors.blue), |
| ), |
| ]; |
| await tester.pumpWidget(boilerplate(slivers)); |
| final RenderBox box1 = tester.renderObject<RenderBox>(find.byType(Container).last); |
| expect(box1.size.height, equals(450)); |
| |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -50.0)); |
| await tester.pump(); |
| final RenderBox box2 = tester.renderObject<RenderBox>(find.byType(Container).last); |
| expect(box2.size.height, equals(450)); |
| }); |
| |
| testWidgets('child with size is overridden and sized by extent', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final List<Widget> slivers = <Widget>[ |
| sliverBox, |
| SliverFillRemaining( |
| hasScrollBody: false, |
| fillOverscroll: true, |
| child: Container( |
| key: key, |
| color: Colors.blue, |
| child: Align( |
| alignment: Alignment.bottomCenter, |
| child: ElevatedButton( |
| child: const Text('bottomCenter button'), |
| onPressed: () {}, |
| ), |
| ), |
| ), |
| ), |
| ]; |
| await tester.pumpWidget(boilerplate(slivers)); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(450), |
| ); |
| |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -50.0)); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(450), |
| ); |
| |
| // Also check that the button alignment is true to expectations |
| final Finder button = find.byType(ElevatedButton); |
| expect(tester.getBottomLeft(button).dy, equals(600.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| }); |
| |
| testWidgets('extent is overridden by child size if precedingScrollExtent > viewportMainAxisExtent', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| final List<Widget> slivers = <Widget>[ |
| SliverFixedExtentList( |
| itemExtent: 150, |
| delegate: SliverChildBuilderDelegate( |
| (BuildContext context, int index) => |
| Container(color: Colors.amber), |
| childCount: 5, |
| ), |
| ), |
| SliverFillRemaining( |
| hasScrollBody: false, |
| fillOverscroll: true, |
| child: Container( |
| key: key, |
| color: Colors.blue[300], |
| child: Align( |
| child: Padding( |
| padding: const EdgeInsets.all(50.0), |
| child: ElevatedButton( |
| child: const Text('center button'), |
| onPressed: () {}, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ]; |
| await tester.pumpWidget(boilerplate(slivers, controller: controller)); |
| |
| // Scroll to the end |
| controller.jumpTo(controller.position.maxScrollExtent); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(148.0), |
| ); |
| |
| // Check that the button alignment is true to expectations |
| final Finder button = find.byType(ElevatedButton); |
| expect(tester.getBottomLeft(button).dy, equals(550.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -50.0)); |
| await tester.pump(); |
| expect( |
| tester.renderObject<RenderBox>(find.byKey(key)).size.height, |
| equals(148.0), |
| ); |
| |
| // Check that the button alignment is still centered |
| expect(tester.getBottomLeft(button).dy, equals(550.0)); |
| expect(tester.getCenter(button).dx, equals(400.0)); |
| }); |
| |
| testWidgets('child has no size and precedingScrollExtent > viewportMainAxisExtent', (WidgetTester tester) async { |
| final GlobalKey key = GlobalKey(); |
| final ScrollController controller = ScrollController(); |
| final List<Widget> slivers = <Widget>[ |
| SliverFixedExtentList( |
| itemExtent: 150, |
| delegate: SliverChildBuilderDelegate( |
| (BuildContext context, int index) { |
| return Semantics(label: index.toString(), child: Container(color: Colors.amber)); |
| }, |
| childCount: 5, |
| ), |
| ), |
| SliverFillRemaining( |
| hasScrollBody: false, |
| fillOverscroll: true, |
| child: Container( |
| key: key, |
| color: Colors.blue, |
| ), |
| ), |
| ]; |
| |
| await tester.pumpWidget(boilerplate(slivers, controller: controller)); |
| |
| expect(find.byKey(key), findsNothing); |
| expect( |
| find.bySemanticsLabel('4'), |
| findsNothing, |
| ); |
| |
| // Scroll to bottom |
| controller.jumpTo(controller.position.maxScrollExtent); |
| await tester.pump(); |
| |
| // End of list |
| expect(find.byKey(key), findsNothing); |
| expect( |
| find.bySemanticsLabel('4'), |
| findsOneWidget, |
| ); |
| |
| // Overscroll |
| await tester.drag(find.byType(Scrollable), const Offset(0.0, -50.0)); |
| await tester.pump(); |
| |
| expect(find.byKey(key), findsNothing); |
| expect( |
| find.bySemanticsLabel('4'), |
| findsOneWidget, |
| ); |
| }); |
| }); |
| }); |
| }); |
| } |