455 lines
18 KiB
Dart
455 lines
18 KiB
Dart
// 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.
|
|
|
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
|
// machines.
|
|
@Tags(<String>['reduced-test-set'])
|
|
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
Widget buildTest(
|
|
GlobalKey box1Key,
|
|
GlobalKey box2Key,
|
|
GlobalKey box3Key,
|
|
ScrollController controller, {
|
|
Axis axis = Axis.vertical,
|
|
bool reverse = false,
|
|
}) {
|
|
final AxisDirection axisDirection;
|
|
switch (axis) {
|
|
case Axis.horizontal:
|
|
axisDirection = reverse ? AxisDirection.left : AxisDirection.right;
|
|
break;
|
|
case Axis.vertical:
|
|
axisDirection = reverse ? AxisDirection.up : AxisDirection.down;
|
|
break;
|
|
}
|
|
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(size: Size(800.0, 600.0)),
|
|
child: ScrollConfiguration(
|
|
behavior: const ScrollBehavior().copyWith(overscroll: false),
|
|
child: StretchingOverscrollIndicator(
|
|
axisDirection: axisDirection,
|
|
child: CustomScrollView(
|
|
reverse: reverse,
|
|
scrollDirection: axis,
|
|
controller: controller,
|
|
slivers: <Widget>[
|
|
SliverToBoxAdapter(child: Container(
|
|
color: const Color(0xD0FF0000),
|
|
key: box1Key,
|
|
height: 250.0,
|
|
width: 300.0,
|
|
)),
|
|
SliverToBoxAdapter(child: Container(
|
|
color: const Color(0xFFFFFF00),
|
|
key: box2Key,
|
|
height: 250.0,
|
|
width: 300.0,
|
|
)),
|
|
SliverToBoxAdapter(child: Container(
|
|
color: const Color(0xFF6200EA),
|
|
key: box3Key,
|
|
height: 250.0,
|
|
width: 300.0,
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
testWidgets('Stretch overscroll vertically', (WidgetTester tester) async {
|
|
final GlobalKey box1Key = GlobalKey();
|
|
final GlobalKey box2Key = GlobalKey();
|
|
final GlobalKey box3Key = GlobalKey();
|
|
final ScrollController controller = ScrollController();
|
|
await tester.pumpWidget(
|
|
buildTest(box1Key, box2Key, box3Key, controller),
|
|
);
|
|
|
|
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
|
|
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
|
|
final RenderBox box1 = tester.renderObject(find.byKey(box1Key));
|
|
final RenderBox box2 = tester.renderObject(find.byKey(box2Key));
|
|
final RenderBox box3 = tester.renderObject(find.byKey(box3Key));
|
|
|
|
expect(controller.offset, 0.0);
|
|
expect(box1.localToGlobal(Offset.zero), Offset.zero);
|
|
expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0));
|
|
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0));
|
|
await expectLater(
|
|
find.byType(CustomScrollView),
|
|
matchesGoldenFile('overscroll_stretch.vertical.start.png'),
|
|
);
|
|
|
|
TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView)));
|
|
// Overscroll the start
|
|
await gesture.moveBy(const Offset(0.0, 200.0));
|
|
await tester.pumpAndSettle();
|
|
expect(box1.localToGlobal(Offset.zero), Offset.zero);
|
|
expect(box2.localToGlobal(Offset.zero).dy, greaterThan(255.0));
|
|
expect(box3.localToGlobal(Offset.zero).dy, greaterThan(510.0));
|
|
await expectLater(
|
|
find.byType(CustomScrollView),
|
|
matchesGoldenFile('overscroll_stretch.vertical.start.stretched.png'),
|
|
);
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Stretch released back to the start
|
|
expect(box1.localToGlobal(Offset.zero), Offset.zero);
|
|
expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0));
|
|
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0));
|
|
|
|
// Jump to end of the list
|
|
controller.jumpTo(controller.position.maxScrollExtent);
|
|
await tester.pumpAndSettle();
|
|
expect(controller.offset, 150.0);
|
|
expect(box1.localToGlobal(Offset.zero).dy, -150.0);
|
|
expect(box2.localToGlobal(Offset.zero).dy, 100.0);
|
|
expect(box3.localToGlobal(Offset.zero).dy, 350.0);
|
|
await expectLater(
|
|
find.byType(CustomScrollView),
|
|
matchesGoldenFile('overscroll_stretch.vertical.end.png'),
|
|
);
|
|
|
|
gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView)));
|
|
// Overscroll the end
|
|
await gesture.moveBy(const Offset(0.0, -200.0));
|
|
await tester.pumpAndSettle();
|
|
expect(box1.localToGlobal(Offset.zero).dy, lessThan(-165));
|
|
expect(box2.localToGlobal(Offset.zero).dy, lessThan(90.0));
|
|
expect(box3.localToGlobal(Offset.zero).dy, lessThan(350.0));
|
|
await expectLater(
|
|
find.byType(CustomScrollView),
|
|
matchesGoldenFile('overscroll_stretch.vertical.end.stretched.png'),
|
|
);
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Stretch released back
|
|
expect(box1.localToGlobal(Offset.zero).dy, -150.0);
|
|
expect(box2.localToGlobal(Offset.zero).dy, 100.0);
|
|
expect(box3.localToGlobal(Offset.zero).dy, 350.0);
|
|
});
|
|
|
|
testWidgets('Stretch overscroll works in reverse - vertical', (WidgetTester tester) async {
|
|
final GlobalKey box1Key = GlobalKey();
|
|
final GlobalKey box2Key = GlobalKey();
|
|
final GlobalKey box3Key = GlobalKey();
|
|
final ScrollController controller = ScrollController();
|
|
await tester.pumpWidget(
|
|
buildTest(box1Key, box2Key, box3Key, controller, reverse: true),
|
|
);
|
|
|
|
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
|
|
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
|
|
final RenderBox box1 = tester.renderObject(find.byKey(box1Key));
|
|
final RenderBox box2 = tester.renderObject(find.byKey(box2Key));
|
|
final RenderBox box3 = tester.renderObject(find.byKey(box3Key));
|
|
|
|
expect(controller.offset, 0.0);
|
|
expect(box1.localToGlobal(Offset.zero), const Offset(0.0, 350.0));
|
|
expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 100.0));
|
|
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, -150.0));
|
|
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView)));
|
|
// Overscroll
|
|
await gesture.moveBy(const Offset(0.0, -200.0));
|
|
await tester.pumpAndSettle();
|
|
expect(box1.localToGlobal(Offset.zero).dy, lessThan(350.0));
|
|
expect(box2.localToGlobal(Offset.zero).dy, lessThan(100.0));
|
|
expect(box3.localToGlobal(Offset.zero).dy, lessThan(-150.0));
|
|
await expectLater(
|
|
find.byType(CustomScrollView),
|
|
matchesGoldenFile('overscroll_stretch.vertical.reverse.png'),
|
|
);
|
|
});
|
|
|
|
testWidgets('Stretch overscroll works in reverse - horizontal', (WidgetTester tester) async {
|
|
final GlobalKey box1Key = GlobalKey();
|
|
final GlobalKey box2Key = GlobalKey();
|
|
final GlobalKey box3Key = GlobalKey();
|
|
final ScrollController controller = ScrollController();
|
|
await tester.pumpWidget(
|
|
buildTest(
|
|
box1Key,
|
|
box2Key,
|
|
box3Key,
|
|
controller,
|
|
axis: Axis.horizontal,
|
|
reverse: true,
|
|
),
|
|
);
|
|
|
|
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
|
|
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
|
|
final RenderBox box1 = tester.renderObject(find.byKey(box1Key));
|
|
final RenderBox box2 = tester.renderObject(find.byKey(box2Key));
|
|
final RenderBox box3 = tester.renderObject(find.byKey(box3Key));
|
|
|
|
expect(controller.offset, 0.0);
|
|
expect(box1.localToGlobal(Offset.zero), const Offset(500.0, 0.0));
|
|
expect(box2.localToGlobal(Offset.zero), const Offset(200.0, 0.0));
|
|
expect(box3.localToGlobal(Offset.zero), const Offset(-100.0, 0.0));
|
|
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView)));
|
|
// Overscroll
|
|
await gesture.moveBy(const Offset(-200.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
expect(box1.localToGlobal(Offset.zero).dx, lessThan(500.0));
|
|
expect(box2.localToGlobal(Offset.zero).dx, lessThan(200.0));
|
|
expect(box3.localToGlobal(Offset.zero).dx, lessThan(-100.0));
|
|
await expectLater(
|
|
find.byType(CustomScrollView),
|
|
matchesGoldenFile('overscroll_stretch.horizontal.reverse.png'),
|
|
);
|
|
});
|
|
|
|
testWidgets('Stretch overscroll horizontally', (WidgetTester tester) async {
|
|
final GlobalKey box1Key = GlobalKey();
|
|
final GlobalKey box2Key = GlobalKey();
|
|
final GlobalKey box3Key = GlobalKey();
|
|
final ScrollController controller = ScrollController();
|
|
await tester.pumpWidget(
|
|
buildTest(box1Key, box2Key, box3Key, controller, axis: Axis.horizontal)
|
|
);
|
|
|
|
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
|
|
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
|
|
final RenderBox box1 = tester.renderObject(find.byKey(box1Key));
|
|
final RenderBox box2 = tester.renderObject(find.byKey(box2Key));
|
|
final RenderBox box3 = tester.renderObject(find.byKey(box3Key));
|
|
|
|
expect(controller.offset, 0.0);
|
|
expect(box1.localToGlobal(Offset.zero), Offset.zero);
|
|
expect(box2.localToGlobal(Offset.zero), const Offset(300.0, 0.0));
|
|
expect(box3.localToGlobal(Offset.zero), const Offset(600.0, 0.0));
|
|
await expectLater(
|
|
find.byType(CustomScrollView),
|
|
matchesGoldenFile('overscroll_stretch.horizontal.start.png'),
|
|
);
|
|
|
|
TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView)));
|
|
// Overscroll the start
|
|
await gesture.moveBy(const Offset(200.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
expect(box1.localToGlobal(Offset.zero), Offset.zero);
|
|
expect(box2.localToGlobal(Offset.zero).dx, greaterThan(305.0));
|
|
expect(box3.localToGlobal(Offset.zero).dx, greaterThan(610.0));
|
|
await expectLater(
|
|
find.byType(CustomScrollView),
|
|
matchesGoldenFile('overscroll_stretch.horizontal.start.stretched.png'),
|
|
);
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Stretch released back to the start
|
|
expect(box1.localToGlobal(Offset.zero), Offset.zero);
|
|
expect(box2.localToGlobal(Offset.zero), const Offset(300.0, 0.0));
|
|
expect(box3.localToGlobal(Offset.zero), const Offset(600.0, 0.0));
|
|
|
|
// Jump to end of the list
|
|
controller.jumpTo(controller.position.maxScrollExtent);
|
|
await tester.pumpAndSettle();
|
|
expect(controller.offset, 100.0);
|
|
expect(box1.localToGlobal(Offset.zero).dx, -100.0);
|
|
expect(box2.localToGlobal(Offset.zero).dx, 200.0);
|
|
expect(box3.localToGlobal(Offset.zero).dx, 500.0);
|
|
await expectLater(
|
|
find.byType(CustomScrollView),
|
|
matchesGoldenFile('overscroll_stretch.horizontal.end.png'),
|
|
);
|
|
|
|
gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView)));
|
|
// Overscroll the end
|
|
await gesture.moveBy(const Offset(-200.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
expect(box1.localToGlobal(Offset.zero).dx, lessThan(-116.0));
|
|
expect(box2.localToGlobal(Offset.zero).dx, lessThan(190.0));
|
|
expect(box3.localToGlobal(Offset.zero).dx, lessThan(500.0));
|
|
await expectLater(
|
|
find.byType(CustomScrollView),
|
|
matchesGoldenFile('overscroll_stretch.horizontal.end.stretched.png'),
|
|
);
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Stretch released back
|
|
expect(box1.localToGlobal(Offset.zero).dx, -100.0);
|
|
expect(box2.localToGlobal(Offset.zero).dx, 200.0);
|
|
expect(box3.localToGlobal(Offset.zero).dx, 500.0);
|
|
});
|
|
|
|
testWidgets('Disallow stretching overscroll', (WidgetTester tester) async {
|
|
final GlobalKey box1Key = GlobalKey();
|
|
final GlobalKey box2Key = GlobalKey();
|
|
final GlobalKey box3Key = GlobalKey();
|
|
final ScrollController controller = ScrollController();
|
|
double indicatorNotification =0;
|
|
await tester.pumpWidget(
|
|
NotificationListener<OverscrollIndicatorNotification>(
|
|
onNotification: (OverscrollIndicatorNotification notification) {
|
|
notification.disallowIndicator();
|
|
indicatorNotification += 1;
|
|
return false;
|
|
},
|
|
child: buildTest(box1Key, box2Key, box3Key, controller),
|
|
)
|
|
);
|
|
|
|
expect(find.byType(StretchingOverscrollIndicator), findsOneWidget);
|
|
expect(find.byType(GlowingOverscrollIndicator), findsNothing);
|
|
final RenderBox box1 = tester.renderObject(find.byKey(box1Key));
|
|
final RenderBox box2 = tester.renderObject(find.byKey(box2Key));
|
|
final RenderBox box3 = tester.renderObject(find.byKey(box3Key));
|
|
|
|
expect(indicatorNotification, 0.0);
|
|
expect(controller.offset, 0.0);
|
|
expect(box1.localToGlobal(Offset.zero), Offset.zero);
|
|
expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0));
|
|
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0));
|
|
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(CustomScrollView)));
|
|
// Overscroll the start, should not stretch
|
|
await gesture.moveBy(const Offset(0.0, 200.0));
|
|
await tester.pumpAndSettle();
|
|
expect(indicatorNotification, 1.0);
|
|
expect(box1.localToGlobal(Offset.zero), Offset.zero);
|
|
expect(box2.localToGlobal(Offset.zero), const Offset(0.0, 250.0));
|
|
expect(box3.localToGlobal(Offset.zero), const Offset(0.0, 500.0));
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Stretch does not overflow bounds of container', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/90197
|
|
await tester.pumpWidget(Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(size: Size(800.0, 600.0)),
|
|
child: ScrollConfiguration(
|
|
behavior: const ScrollBehavior().copyWith(overscroll: false),
|
|
child: Column(
|
|
children: <Widget>[
|
|
StretchingOverscrollIndicator(
|
|
axisDirection: AxisDirection.down,
|
|
child: SizedBox(
|
|
height: 300,
|
|
child: ListView.builder(
|
|
itemCount: 20,
|
|
itemBuilder: (BuildContext context, int index){
|
|
return Padding(
|
|
padding: const EdgeInsets.all(10.0),
|
|
child: Text('Index $index'),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
Opacity(
|
|
opacity: 0.5,
|
|
child: Container(
|
|
color: const Color(0xD0FF0000),
|
|
height: 100,
|
|
),
|
|
)
|
|
],
|
|
)
|
|
),
|
|
)
|
|
));
|
|
|
|
expect(find.text('Index 1'), findsOneWidget);
|
|
expect(tester.getCenter(find.text('Index 1')).dy, 51.0);
|
|
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Index 1')));
|
|
// Overscroll the start.
|
|
await gesture.moveBy(const Offset(0.0, 200.0));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Index 1'), findsOneWidget);
|
|
expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(0));
|
|
// Image should not show the text overlapping the red area below the list.
|
|
await expectLater(
|
|
find.byType(Column),
|
|
matchesGoldenFile('overscroll_stretch.no_overflow.png'),
|
|
);
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Clip behavior is updated as needed', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/97867
|
|
await tester.pumpWidget(Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(size: Size(800.0, 600.0)),
|
|
child: ScrollConfiguration(
|
|
behavior: const ScrollBehavior().copyWith(overscroll: false),
|
|
child: Column(
|
|
children: <Widget>[
|
|
StretchingOverscrollIndicator(
|
|
axisDirection: AxisDirection.down,
|
|
child: SizedBox(
|
|
height: 300,
|
|
child: ListView.builder(
|
|
itemCount: 20,
|
|
itemBuilder: (BuildContext context, int index){
|
|
return Padding(
|
|
padding: const EdgeInsets.all(10.0),
|
|
child: Text('Index $index'),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
Opacity(
|
|
opacity: 0.5,
|
|
child: Container(
|
|
color: const Color(0xD0FF0000),
|
|
height: 100,
|
|
),
|
|
)
|
|
],
|
|
)
|
|
),
|
|
)
|
|
));
|
|
|
|
expect(find.text('Index 1'), findsOneWidget);
|
|
expect(tester.getCenter(find.text('Index 1')).dy, 51.0);
|
|
RenderClipRect renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
|
|
// Currently not clipping
|
|
expect(renderClip.clipBehavior, equals(Clip.none));
|
|
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.text('Index 1')));
|
|
// Overscroll the start.
|
|
await gesture.moveBy(const Offset(0.0, 200.0));
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('Index 1'), findsOneWidget);
|
|
expect(tester.getCenter(find.text('Index 1')).dy, greaterThan(0));
|
|
renderClip = tester.allRenderObjects.whereType<RenderClipRect>().first;
|
|
// Now clipping
|
|
expect(renderClip.clipBehavior, equals(Clip.hardEdge));
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
});
|
|
}
|