blob: 50f0bd4aaec922d80db6009656237d1445d5f691 [file] [log] [blame]
// 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);
});
}