// 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 'dart:async'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; Widget _buildScroller({ required List<String> log }) { return NotificationListener<ScrollNotification>( onNotification: (ScrollNotification notification) { if (notification is ScrollStartNotification) { log.add('scroll-start'); } else if (notification is ScrollUpdateNotification) { log.add('scroll-update'); } else if (notification is ScrollEndNotification) { log.add('scroll-end'); } return false; }, child: const SingleChildScrollView( child: SizedBox(width: 1000.0, height: 1000.0), ), ); } void main() { Completer<void> animateTo(WidgetTester tester, double newScrollOffset, { required Duration duration }) { final Completer<void> completer = Completer<void>(); final ScrollableState scrollable = tester.state(find.byType(Scrollable)); scrollable.position.animateTo(newScrollOffset, duration: duration, curve: Curves.linear).whenComplete(completer.complete); return completer; } void jumpTo(WidgetTester tester, double newScrollOffset) { final ScrollableState scrollable = tester.state(find.byType(Scrollable)); scrollable.position.jumpTo(newScrollOffset); } testWidgets('Scroll event drag', (WidgetTester tester) async { final List<String> log = <String>[]; await tester.pumpWidget(_buildScroller(log: log)); expect(log, equals(<String>[])); final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0)); expect(log, equals(<String>['scroll-start'])); await tester.pump(const Duration(seconds: 1)); expect(log, equals(<String>['scroll-start'])); await gesture.moveBy(const Offset(-10.0, -10.0)); expect(log, equals(<String>['scroll-start', 'scroll-update'])); await tester.pump(const Duration(seconds: 1)); expect(log, equals(<String>['scroll-start', 'scroll-update'])); await gesture.up(); expect(log, equals(<String>['scroll-start', 'scroll-update', 'scroll-end'])); await tester.pump(const Duration(seconds: 1)); expect(log, equals(<String>['scroll-start', 'scroll-update', 'scroll-end'])); }); testWidgets('Scroll animateTo', (WidgetTester tester) async { final List<String> log = <String>[]; await tester.pumpWidget(_buildScroller(log: log)); expect(log, equals(<String>[])); final Completer<void> completer = animateTo(tester, 100.0, duration: const Duration(seconds: 1)); expect(completer.isCompleted, isFalse); expect(log, equals(<String>['scroll-start'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(<String>['scroll-start'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(<String>['scroll-start', 'scroll-update'])); await tester.pump(const Duration(milliseconds: 1500)); expect(log, equals(<String>['scroll-start', 'scroll-update', 'scroll-update', 'scroll-end'])); expect(completer.isCompleted, isTrue); }); testWidgets('Scroll jumpTo', (WidgetTester tester) async { final List<String> log = <String>[]; await tester.pumpWidget(_buildScroller(log: log)); expect(log, equals(<String>[])); jumpTo(tester, 100.0); expect(log, equals(<String>['scroll-start', 'scroll-update', 'scroll-end'])); await tester.pump(); expect(log, equals(<String>['scroll-start', 'scroll-update', 'scroll-end'])); }); testWidgets('Scroll jumpTo during animation', (WidgetTester tester) async { final List<String> log = <String>[]; await tester.pumpWidget(_buildScroller(log: log)); expect(log, equals(<String>[])); final Completer<void> completer = animateTo(tester, 100.0, duration: const Duration(seconds: 1)); expect(completer.isCompleted, isFalse); expect(log, equals(<String>['scroll-start'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(<String>['scroll-start'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(<String>['scroll-start', 'scroll-update'])); expect(completer.isCompleted, isFalse); jumpTo(tester, 100.0); expect(completer.isCompleted, isFalse); expect(log, equals(<String>['scroll-start', 'scroll-update', 'scroll-end', 'scroll-start', 'scroll-update', 'scroll-end'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(<String>['scroll-start', 'scroll-update', 'scroll-end', 'scroll-start', 'scroll-update', 'scroll-end'])); expect(completer.isCompleted, isTrue); await tester.pump(const Duration(milliseconds: 1500)); expect(log, equals(<String>['scroll-start', 'scroll-update', 'scroll-end', 'scroll-start', 'scroll-update', 'scroll-end'])); expect(completer.isCompleted, isTrue); }); testWidgets('Scroll scrollTo during animation', (WidgetTester tester) async { final List<String> log = <String>[]; await tester.pumpWidget(_buildScroller(log: log)); expect(log, equals(<String>[])); Completer<void> completer = animateTo(tester, 100.0, duration: const Duration(seconds: 1)); expect(completer.isCompleted, isFalse); expect(log, equals(<String>['scroll-start'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(<String>['scroll-start'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(<String>['scroll-start', 'scroll-update'])); expect(completer.isCompleted, isFalse); completer = animateTo(tester, 100.0, duration: const Duration(seconds: 1)); expect(completer.isCompleted, isFalse); expect(log, equals(<String>['scroll-start', 'scroll-update'])); await tester.pump(const Duration(milliseconds: 100)); expect(log, equals(<String>['scroll-start', 'scroll-update'])); await tester.pump(const Duration(milliseconds: 1500)); expect(log, equals(<String>['scroll-start', 'scroll-update', 'scroll-update', 'scroll-end'])); expect(completer.isCompleted, isTrue); }); testWidgets('fling, fling generates two start/end pairs', (WidgetTester tester) async { final List<String> log = <String>[]; await tester.pumpWidget(_buildScroller(log: log)); // The ideal behavior here would be a single start/end pair, but for // simplicity of implementation we compromise here and accept two. Should // you find a way to make this work with just one without complicating the // API, feel free to change the expectation here. expect(log, equals(<String>[])); await tester.flingFrom(const Offset(100.0, 100.0), const Offset(-50.0, -50.0), 500.0); await tester.pump(const Duration(seconds: 1)); log.removeWhere((String value) => value == 'scroll-update'); expect(log, equals(<String>['scroll-start'])); await tester.flingFrom(const Offset(100.0, 100.0), const Offset(-50.0, -50.0), 500.0); log.removeWhere((String value) => value == 'scroll-update'); expect(log, equals(<String>['scroll-start', 'scroll-end', 'scroll-start'])); await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1)); log.removeWhere((String value) => value == 'scroll-update'); expect(log, equals(<String>['scroll-start', 'scroll-end', 'scroll-start', 'scroll-end'])); }); testWidgets('fling, pause, fling generates two start/end pairs', (WidgetTester tester) async { final List<String> log = <String>[]; await tester.pumpWidget(_buildScroller(log: log)); expect(log, equals(<String>[])); await tester.flingFrom(const Offset(100.0, 100.0), const Offset(-50.0, -50.0), 500.0); await tester.pump(const Duration(seconds: 1)); log.removeWhere((String value) => value == 'scroll-update'); expect(log, equals(<String>['scroll-start'])); await tester.pump(const Duration(minutes: 1)); await tester.flingFrom(const Offset(100.0, 100.0), const Offset(-50.0, -50.0), 500.0); log.removeWhere((String value) => value == 'scroll-update'); expect(log, equals(<String>['scroll-start', 'scroll-end', 'scroll-start'])); await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1)); log.removeWhere((String value) => value == 'scroll-update'); expect(log, equals(<String>['scroll-start', 'scroll-end', 'scroll-start', 'scroll-end'])); }); testWidgets('fling up ends', (WidgetTester tester) async { final List<String> log = <String>[]; await tester.pumpWidget(_buildScroller(log: log)); expect(log, equals(<String>[])); await tester.flingFrom(const Offset(100.0, 100.0), const Offset(50.0, 50.0), 500.0); await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1)); await tester.pump(const Duration(seconds: 1)); expect(log.first, equals('scroll-start')); expect(log.last, equals('scroll-end')); log.removeWhere((String value) => value == 'scroll-update'); expect(log.length, equals(2)); expect(tester.state<ScrollableState>(find.byType(Scrollable)).position.pixels, equals(0.0)); }); }