blob: 4ca2647f48138479de5de5bb413a9bc63aef27fa [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/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
const CupertinoDynamicColor _kScrollbarColor = CupertinoDynamicColor.withBrightness(
color: Color(0x59000000),
darkColor: Color(0x80FFFFFF),
);
void main() {
const Duration _kScrollbarTimeToFade = Duration(milliseconds: 1200);
const Duration _kScrollbarFadeDuration = Duration(milliseconds: 250);
const Duration _kScrollbarResizeDuration = Duration(milliseconds: 100);
testWidgets('Scrollbar never goes away until finger lift', (WidgetTester tester) async {
await tester.pumpWidget(
const Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: MediaQueryData(),
child: CupertinoScrollbar(
child: SingleChildScrollView(child: SizedBox(width: 4000.0, height: 4000.0)),
),
),
),
);
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(SingleChildScrollView)));
await gesture.moveBy(const Offset(0.0, -10.0));
await tester.pump();
// Scrollbar fully showing
await tester.pump(const Duration(milliseconds: 500));
expect(find.byType(CupertinoScrollbar), paints..rrect(
color: _kScrollbarColor.color,
));
await tester.pump(const Duration(seconds: 3));
await tester.pump(const Duration(seconds: 3));
// Still there.
expect(find.byType(CupertinoScrollbar), paints..rrect(
color: _kScrollbarColor.color,
));
await gesture.up();
await tester.pump(_kScrollbarTimeToFade);
await tester.pump(_kScrollbarFadeDuration * 0.5);
// Opacity going down now.
expect(find.byType(CupertinoScrollbar), paints..rrect(
color: _kScrollbarColor.color.withAlpha(69),
));
});
testWidgets('Scrollbar dark mode', (WidgetTester tester) async {
Brightness brightness = Brightness.light;
StateSetter setState;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setter) {
setState = setter;
return MediaQuery(
data: MediaQueryData(platformBrightness: brightness),
child: const CupertinoScrollbar(
child: SingleChildScrollView(child: SizedBox(width: 4000.0, height: 4000.0)),
),
);
},
),
),
);
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(SingleChildScrollView)));
await gesture.moveBy(const Offset(0.0, 10.0));
await tester.pump();
// Scrollbar fully showing
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), paints..rrect(
color: _kScrollbarColor.color,
));
setState(() { brightness = Brightness.dark; });
await tester.pump();
expect(find.byType(CupertinoScrollbar), paints..rrect(
color: _kScrollbarColor.darkColor,
));
});
testWidgets('Scrollbar thumb can be dragged with long press', (WidgetTester tester) async {
final ScrollController scrollController = ScrollController();
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: PrimaryScrollController(
controller: scrollController,
child: const CupertinoScrollbar(
child: SingleChildScrollView(child: SizedBox(width: 4000.0, height: 4000.0)),
),
),
),
),
);
expect(scrollController.offset, 0.0);
// Scroll a bit.
const double scrollAmount = 10.0;
final TestGesture scrollGesture = await tester.startGesture(tester.getCenter(find.byType(SingleChildScrollView)));
// Scroll down by swiping up.
await scrollGesture.moveBy(const Offset(0.0, -scrollAmount));
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
// Scrollbar thumb is fully showing and scroll offset has moved by
// scrollAmount.
expect(find.byType(CupertinoScrollbar), paints..rrect(
color: _kScrollbarColor.color,
));
expect(scrollController.offset, scrollAmount);
await scrollGesture.up();
await tester.pump();
int hapticFeedbackCalls = 0;
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
if (methodCall.method == 'HapticFeedback.vibrate') {
hapticFeedbackCalls++;
}
});
// Longpress on the scrollbar thumb and expect a vibration after it resizes.
expect(hapticFeedbackCalls, 0);
final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(796.0, 50.0));
await tester.pump(const Duration(milliseconds: 100));
expect(hapticFeedbackCalls, 0);
await tester.pump(_kScrollbarResizeDuration);
// Allow the haptic feedback some slack.
await tester.pump(const Duration(milliseconds: 1));
expect(hapticFeedbackCalls, 1);
// Drag the thumb down to scroll down.
await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount));
await tester.pump(const Duration(milliseconds: 100));
await dragScrollbarGesture.up();
await tester.pumpAndSettle();
// The view has scrolled more than it would have by a swipe gesture of the
// same distance.
expect(scrollController.offset, greaterThan(scrollAmount * 2));
// The scrollbar thumb is still fully visible.
expect(find.byType(CupertinoScrollbar), paints..rrect(
color: _kScrollbarColor.color,
));
// Let the thumb fade out so all timers have resolved.
await tester.pump(_kScrollbarTimeToFade);
await tester.pump(_kScrollbarFadeDuration);
});
testWidgets('On first render with isAlwaysShown: true, the thumb shows',
(WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll() {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: PrimaryScrollController(
controller: controller,
child: CupertinoScrollbar(
isAlwaysShown: true,
controller: controller,
child: const SingleChildScrollView(
child: SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
),
),
);
}
await tester.pumpWidget(viewWithScroll());
// The scrollbar measures its size on the first frame
// and renders starting in the second,
//
// so pumpAndSettle a frame to allow it to appear.
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), paints..rrect());
});
testWidgets('On first render with isAlwaysShown: false, the thumb is hidden',
(WidgetTester tester) async {
final ScrollController controller = ScrollController();
Widget viewWithScroll() {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: PrimaryScrollController(
controller: controller,
child: CupertinoScrollbar(
isAlwaysShown: false,
controller: controller,
child: const SingleChildScrollView(
child: SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
),
),
);
}
await tester.pumpWidget(viewWithScroll());
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), isNot(paints..rect()));
});
testWidgets(
'With isAlwaysShown: true, fling a scroll. While it is still scrolling, set isAlwaysShown: false. The thumb should not fade out until the scrolling stops.',
(WidgetTester tester) async {
final ScrollController controller = ScrollController();
bool isAlwaysShown = true;
Widget viewWithScroll() {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: Stack(
children: <Widget>[
CupertinoScrollbar(
isAlwaysShown: isAlwaysShown,
controller: controller,
child: SingleChildScrollView(
controller: controller,
child: const SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
Positioned(
bottom: 10,
child: CupertinoButton(
onPressed: () {
setState(() {
isAlwaysShown = !isAlwaysShown;
});
},
child: const Text('change isAlwaysShown'),
),
)
],
),
),
);
},
);
}
await tester.pumpWidget(viewWithScroll());
await tester.pumpAndSettle();
await tester.fling(
find.byType(SingleChildScrollView),
const Offset(0.0, -10.0),
10,
);
expect(find.byType(CupertinoScrollbar), paints..rrect());
await tester.tap(find.byType(CupertinoButton));
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), isNot(paints..rrect()));
});
testWidgets(
'With isAlwaysShown: false, set isAlwaysShown: true. The thumb should be always shown directly',
(WidgetTester tester) async {
final ScrollController controller = ScrollController();
bool isAlwaysShown = false;
Widget viewWithScroll() {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: Stack(
children: <Widget>[
CupertinoScrollbar(
isAlwaysShown: isAlwaysShown,
controller: controller,
child: SingleChildScrollView(
controller: controller,
child: const SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
Positioned(
bottom: 10,
child: CupertinoButton(
onPressed: () {
setState(() {
isAlwaysShown = !isAlwaysShown;
});
},
child: const Text('change isAlwaysShown'),
),
)
],
),
),
);
},
);
}
await tester.pumpWidget(viewWithScroll());
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), isNot(paints..rrect()));
await tester.tap(find.byType(CupertinoButton));
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), paints..rrect());
});
testWidgets(
'With isAlwaysShown: false, fling a scroll. While it is still scrolling, set isAlwaysShown: true. The thumb should not fade even after the scrolling stops',
(WidgetTester tester) async {
final ScrollController controller = ScrollController();
bool isAlwaysShown = false;
Widget viewWithScroll() {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: Stack(
children: <Widget>[
CupertinoScrollbar(
isAlwaysShown: isAlwaysShown,
controller: controller,
child: SingleChildScrollView(
controller: controller,
child: const SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
Positioned(
bottom: 10,
child: CupertinoButton(
onPressed: () {
setState(() {
isAlwaysShown = !isAlwaysShown;
});
},
child: const Text('change isAlwaysShown'),
),
)
],
),
),
);
},
);
}
await tester.pumpWidget(viewWithScroll());
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), isNot(paints..rrect()));
await tester.fling(
find.byType(SingleChildScrollView),
const Offset(0.0, -10.0),
10,
);
expect(find.byType(CupertinoScrollbar), paints..rrect());
await tester.tap(find.byType(CupertinoButton));
await tester.pump();
expect(find.byType(CupertinoScrollbar), paints..rrect());
// Wait for the timer delay to expire.
await tester.pump(const Duration(milliseconds: 600)); // _kScrollbarTimeToFade
await tester.pumpAndSettle();
// Scrollbar thumb is showing after scroll finishes and timer ends.
expect(find.byType(CupertinoScrollbar), paints..rrect());
});
testWidgets(
'Toggling isAlwaysShown while not scrolling fades the thumb in/out. This works even when you have never scrolled at all yet',
(WidgetTester tester) async {
final ScrollController controller = ScrollController();
bool isAlwaysShown = true;
Widget viewWithScroll() {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: Stack(
children: <Widget>[
CupertinoScrollbar(
isAlwaysShown: isAlwaysShown,
controller: controller,
child: SingleChildScrollView(
controller: controller,
child: const SizedBox(
width: 4000.0,
height: 4000.0,
),
),
),
Positioned(
bottom: 10,
child: CupertinoButton(
onPressed: () {
setState(() {
isAlwaysShown = !isAlwaysShown;
});
},
child: const Text('change isAlwaysShown'),
),
)
],
),
),
);
},
);
}
await tester.pumpWidget(viewWithScroll());
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), paints..rrect());
await tester.tap(find.byType(CupertinoButton));
await tester.pumpAndSettle();
expect(find.byType(CupertinoScrollbar), isNot(paints..rrect()));
});
}