| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/rendering.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| class TestSliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate { |
| TestSliverPersistentHeaderDelegate(this._maxExtent); |
| |
| final double _maxExtent; |
| |
| @override |
| double get maxExtent => _maxExtent; |
| |
| @override |
| double get minExtent => 16.0; |
| |
| @override |
| Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) { |
| return Column( |
| children: <Widget>[ |
| Container(height: minExtent), |
| Expanded(child: Container()), |
| ], |
| ); |
| } |
| |
| @override |
| bool shouldRebuild(TestSliverPersistentHeaderDelegate oldDelegate) => false; |
| } |
| |
| class TestBehavior extends ScrollBehavior { |
| const TestBehavior(); |
| |
| @override |
| Widget buildOverscrollIndicator(BuildContext context, Widget child, ScrollableDetails details) { |
| return GlowingOverscrollIndicator( |
| axisDirection: details.direction, |
| color: const Color(0xFFFFFFFF), |
| child: child, |
| ); |
| } |
| } |
| |
| class TestScrollPhysics extends ClampingScrollPhysics { |
| const TestScrollPhysics({ ScrollPhysics? parent }) : super(parent: parent); |
| |
| @override |
| TestScrollPhysics applyTo(ScrollPhysics? ancestor) { |
| return TestScrollPhysics(parent: parent?.applyTo(ancestor) ?? ancestor); |
| } |
| |
| @override |
| Tolerance get tolerance => const Tolerance(velocity: 20.0, distance: 1.0); |
| } |
| |
| class TestViewportScrollPosition extends ScrollPositionWithSingleContext { |
| TestViewportScrollPosition({ |
| required ScrollPhysics physics, |
| required ScrollContext context, |
| ScrollPosition? oldPosition, |
| }) : super(physics: physics, context: context, oldPosition: oldPosition); |
| |
| @override |
| bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { |
| expect(minScrollExtent, moreOrLessEquals(-3895.0)); |
| expect(maxScrollExtent, moreOrLessEquals(8575.0)); |
| return super.applyContentDimensions(minScrollExtent, maxScrollExtent); |
| } |
| } |
| |
| void main() { |
| testWidgets('Evil test of sliver features - 1', (WidgetTester tester) async { |
| final GlobalKey centerKey = GlobalKey(); |
| await tester.pumpWidget( |
| MediaQuery( |
| data: const MediaQueryData(), |
| child: Directionality( |
| textDirection: TextDirection.ltr, |
| child: ScrollConfiguration( |
| behavior: const TestBehavior(), |
| child: Scrollbar( |
| child: Scrollable( |
| physics: const TestScrollPhysics(), |
| viewportBuilder: (BuildContext context, ViewportOffset offset) { |
| return Viewport( |
| anchor: 0.25, |
| offset: offset, |
| center: centerKey, |
| slivers: <Widget>[ |
| SliverToBoxAdapter(child: Container(height: 5.0)), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(150.0), pinned: true), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverPadding( |
| padding: const EdgeInsets.all(50.0), |
| sliver: SliverToBoxAdapter(child: Container(height: 520.0)), |
| ), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(150.0), floating: true), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverToBoxAdapter(key: centerKey, child: Container(height: 520.0)), // ------------------------ CENTER ------------------------ |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(150.0), pinned: true), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverPadding( |
| padding: const EdgeInsets.all(50.0), |
| sliver: SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(250.0), pinned: true), |
| ), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(250.0), pinned: true), |
| SliverToBoxAdapter(child: Container(height: 5.0)), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(250.0), pinned: true), |
| SliverToBoxAdapter(child: Container(height: 5.0)), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(250.0), pinned: true), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(250.0), pinned: true), |
| SliverToBoxAdapter(child: Container(height: 5.0)), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(250.0), pinned: true), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(250.0)), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(150.0), floating: true), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(150.0), floating: true), |
| SliverToBoxAdapter(child: Container(height: 5.0)), |
| SliverList( |
| delegate: SliverChildListDelegate(<Widget>[ |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| Container(height: 50.0), |
| ]), |
| ), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(250.0)), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(250.0)), |
| SliverPersistentHeader(delegate: TestSliverPersistentHeaderDelegate(250.0)), |
| SliverPadding( |
| padding: const EdgeInsets.symmetric(horizontal: 50.0), |
| sliver: SliverToBoxAdapter(child: Container(height: 520.0)), |
| ), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverToBoxAdapter(child: Container(height: 520.0)), |
| SliverToBoxAdapter(child: Container(height: 5.0)), |
| ], |
| ); |
| }, |
| ), |
| ), |
| ), |
| ), |
| ), |
| ); |
| final ScrollPosition position = tester.state<ScrollableState>(find.byType(Scrollable)).position; |
| |
| position.animateTo(10000.0, curve: Curves.linear, duration: const Duration(minutes: 1)); |
| await tester.pump(const Duration(milliseconds: 10)); |
| await tester.pump(const Duration(milliseconds: 10)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| await tester.pumpAndSettle(const Duration(milliseconds: 122)); |
| |
| position.animateTo(-10000.0, curve: Curves.linear, duration: const Duration(minutes: 1)); |
| await tester.pump(const Duration(milliseconds: 10)); |
| await tester.pump(const Duration(milliseconds: 10)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| await tester.pumpAndSettle(const Duration(milliseconds: 122)); |
| |
| position.animateTo(10000.0, curve: Curves.linear, duration: const Duration(minutes: 1)); |
| await tester.pump(const Duration(milliseconds: 10)); |
| await tester.pump(const Duration(milliseconds: 10)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| await tester.pumpAndSettle(const Duration(milliseconds: 122)); |
| |
| position.animateTo(-10000.0, curve: Curves.linear, duration: const Duration(seconds: 1)); |
| await tester.pump(const Duration(milliseconds: 10)); |
| await tester.pump(const Duration(milliseconds: 10)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| await tester.pumpAndSettle(const Duration(milliseconds: 122)); |
| |
| position.animateTo(10000.0, curve: Curves.linear, duration: const Duration(seconds: 1)); |
| await tester.pump(const Duration(milliseconds: 10)); |
| await tester.pump(const Duration(milliseconds: 10)); |
| await tester.pump(const Duration(milliseconds: 50)); |
| await tester.pumpAndSettle(const Duration(milliseconds: 122)); |
| |
| }); |
| |
| testWidgets('Removing offscreen items above and rescrolling does not crash', (WidgetTester tester) async { |
| await tester.pumpWidget(MaterialApp( |
| home: CustomScrollView( |
| cacheExtent: 0.0, |
| slivers: <Widget>[ |
| SliverFixedExtentList( |
| itemExtent: 100.0, |
| delegate: SliverChildBuilderDelegate( |
| (BuildContext context, int index) { |
| return Container( |
| color: Colors.blue, |
| child: Text(index.toString()), |
| ); |
| }, |
| childCount: 30, |
| ), |
| ), |
| ], |
| ), |
| )); |
| |
| await tester.drag(find.text('5'), const Offset(0.0, -500.0)); |
| await tester.pump(); |
| |
| // Screen is 600px high. Moved bottom item 500px up. It's now at the top. |
| expect(tester.getTopLeft(find.widgetWithText(Container, '5')).dy, 0.0); |
| expect(tester.getBottomLeft(find.widgetWithText(Container, '10')).dy, 600.0); |
| |
| // Stop returning the first 3 items. |
| await tester.pumpWidget(MaterialApp( |
| home: CustomScrollView( |
| cacheExtent: 0.0, |
| slivers: <Widget>[ |
| SliverFixedExtentList( |
| itemExtent: 100.0, |
| delegate: SliverChildBuilderDelegate( |
| (BuildContext context, int index) { |
| if (index > 3) { |
| return Container( |
| color: Colors.blue, |
| child: Text(index.toString()), |
| ); |
| } |
| return null; |
| }, |
| childCount: 30, |
| ), |
| ), |
| ], |
| ), |
| )); |
| |
| await tester.drag(find.text('5'), const Offset(0.0, 400.0)); |
| await tester.pump(); |
| |
| // Move up by 4 items, meaning item 1 would have been at the top but |
| // 0 through 3 no longer exist, so item 4, 3 items down, is the first one. |
| // Item 4 is also shifted to the top. |
| expect(tester.getTopLeft(find.widgetWithText(Container, '4')).dy, 0.0); |
| |
| // Because the screen is still 600px, item 9 is now visible at the bottom instead |
| // of what's supposed to be item 6 had we not re-shifted. |
| expect(tester.getBottomLeft(find.widgetWithText(Container, '9')).dy, 600.0); |
| }); |
| } |