import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:krow_worker_app/main.dart'; /// A list whose content is shorter than the viewport, so there is nothing to /// scroll to. Widget shortList() => MaterialApp( scrollBehavior: const KrowScrollBehavior(), home: Scaffold( body: ListView( children: const [SizedBox(height: 100, child: Text('only item'))], ), ), ); Widget longList() => MaterialApp( scrollBehavior: const KrowScrollBehavior(), home: Scaffold( body: ListView( children: [ for (var i = 0; i < 40; i++) SizedBox(height: 100, child: Text('$i')), ], ), ), ); ScrollPosition positionOf(WidgetTester tester) => tester.state(find.byType(Scrollable)).position; void main() { // TargetPlatformVariant sets and restores the override around each test; // assigning it by hand trips the framework's debug-variable invariant check. final android = TargetPlatformVariant.only(TargetPlatform.android); final ios = TargetPlatformVariant.only(TargetPlatform.iOS); group('on Android', () { testWidgets('content that already fits does not move when dragged', ( tester, ) async { await tester.pumpWidget(shortList()); final position = positionOf(tester); // Every vertical list without a controller is handed an // AlwaysScrollableScrollPhysics by ScrollView itself, so the drag is // accepted whatever the content. Clamping is what pins it to the // boundary. Forcing bouncing globally instead let a one-item list be // dragged hundreds of pixels down, leaving a gap under the header. await tester.drag(find.text('only item'), const Offset(0, 300)); await tester.pump(); expect(position.pixels, 0); }, variant: android); testWidgets('a list that overflows still scrolls', (tester) async { await tester.pumpWidget(longList()); final position = positionOf(tester); await tester.drag(find.text('0'), const Offset(0, -200)); await tester.pump(); expect(position.pixels, greaterThan(0)); }, variant: android); testWidgets('and does not overscroll past the top', (tester) async { await tester.pumpWidget(longList()); final position = positionOf(tester); await tester.drag(find.byType(Scrollable), const Offset(0, 400)); await tester.pump(); expect(position.pixels, 0); }, variant: android); }); group('on iOS', () { testWidgets('short content rubber-bands, which is the convention there', ( tester, ) async { await tester.pumpWidget(shortList()); final position = positionOf(tester); await tester.drag(find.text('only item'), const Offset(0, 300)); await tester.pump(); expect(position.pixels, lessThan(0)); // And springs back on release, rather than leaving the gap behind. await tester.pumpAndSettle(); expect(position.pixels, 0); }, variant: ios); }); }