// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; void main() { testWidgets('SliverFillRemaining - 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('SliverFillRemaining - 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)); }); group('SliverFillRemaining - hasScrollBody', () { final Widget sliverBox = SliverToBoxAdapter( child: Container( color: Colors.amber, height: 150.0, ), ); Widget boilerplate(List<Widget> slivers, {ScrollController controller}) { return MaterialApp( home: Scaffold( body: CustomScrollView( slivers: slivers, controller: controller, ), ), ); } testWidgets('does not extend past viewport when false', (WidgetTester tester) async { final ScrollController controller = ScrollController(); final List<Widget> slivers = <Widget>[ sliverBox, SliverFillRemaining( child: Container(color: Colors.white), hasScrollBody: false, ), ]; 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('scrolls beyond viewport by default', (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); }); // SliverFillRemaining considers child size when hasScrollBody: false testWidgets('child without size is sized by extent when false', (WidgetTester tester) async { final List<Widget> slivers = <Widget>[ sliverBox, SliverFillRemaining( hasScrollBody: false, child: Container(color: Colors.blue), ), ]; await tester.pumpWidget(boilerplate(slivers)); final RenderBox box = tester.renderObject<RenderBox>(find.byType(Container).last); expect(box.size.height, equals(450)); }); testWidgets('child with size is sized by extent when false', (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: RaisedButton( 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(RaisedButton); expect(tester.getBottomLeft(button).dy, equals(600.0)); expect(tester.getCenter(button).dx, equals(400.0)); }); testWidgets('extent is overridden by child with larger size when false', (WidgetTester tester) async { final List<Widget> slivers = <Widget>[ sliverBox, SliverFillRemaining( hasScrollBody: false, child: Container( color: Colors.blue, height: 600, ), ), ]; await tester.pumpWidget(boilerplate(slivers)); final RenderBox box = tester.renderObject<RenderBox>(find.byType(Container).last); expect(box.size.height, equals(600)); }); testWidgets('extent is overridden by child size if precedingScrollExtent > viewportMainAxisExtent when false', (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( alignment: Alignment.center, child: Padding( padding: const EdgeInsets.all(50.0), child: RaisedButton( 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(RaisedButton); expect(tester.getBottomLeft(button).dy, equals(550.0)); expect(tester.getCenter(button).dx, equals(400.0)); }); // iOS/Similar scroll physics when hasScrollBody: false & fillOverscroll: true behavior testWidgets('child without size is sized by extent and overscroll', (WidgetTester tester) async { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; 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, greaterThan(450)); debugDefaultTargetPlatformOverride = null; }); testWidgets('child with size is overridden and sized by extent and overscroll', (WidgetTester tester) async { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; 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: RaisedButton( 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(RaisedButton); expect(tester.getBottomLeft(button).dy, equals(600.0)); expect(tester.getCenter(button).dx, equals(400.0)); debugDefaultTargetPlatformOverride = null; }); testWidgets('extent is overridden by child size and overscroll if precedingScrollExtent > viewportMainAxisExtent', (WidgetTester tester) async { debugDefaultTargetPlatformOverride = TargetPlatform.iOS; 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( alignment: Alignment.center, child: Padding( padding: const EdgeInsets.all(50.0), child: RaisedButton( 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(RaisedButton); expect(tester.getBottomLeft(button).dy, equals(550.0)); expect(tester.getCenter(button).dx, equals(400.0)); debugDefaultTargetPlatformOverride = null; // 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)); debugDefaultTargetPlatformOverride = null; }); // Android/Other scroll physics when hasScrollBody: false, ignores fillOverscroll: true testWidgets('child without size is sized by extent, fillOverscroll is ignored', (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, fillOverscroll is ignored', (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: RaisedButton( 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(RaisedButton); 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, fillOverscroll is ignored', (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( alignment: Alignment.center, child: Padding( padding: const EdgeInsets.all(50.0), child: RaisedButton( 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(RaisedButton); expect(tester.getBottomLeft(button).dy, equals(550.0)); expect(tester.getCenter(button).dx, equals(400.0)); debugDefaultTargetPlatformOverride = null; 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 in stretched child expect(tester.getBottomLeft(button).dy, equals(550.0)); expect(tester.getCenter(button).dx, equals(400.0)); }); }); }