| // 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/foundation.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter/services.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| final LogicalKeyboardKey modifierKey = defaultTargetPlatform == TargetPlatform.macOS |
| ? LogicalKeyboardKey.metaLeft |
| : LogicalKeyboardKey.controlLeft; |
| |
| void main() { |
| group('ScrollableDetails', (){ |
| final ScrollController controller = ScrollController(); |
| test('copyWith / == / hashCode', () { |
| final ScrollableDetails details = ScrollableDetails( |
| direction: AxisDirection.down, |
| controller: controller, |
| physics: const AlwaysScrollableScrollPhysics(), |
| decorationClipBehavior: Clip.hardEdge, |
| ); |
| ScrollableDetails copiedDetails = details.copyWith(); |
| expect(details, copiedDetails); |
| expect(details.hashCode, copiedDetails.hashCode); |
| |
| copiedDetails = details.copyWith( |
| direction: AxisDirection.left, |
| physics: const ClampingScrollPhysics(), |
| decorationClipBehavior: Clip.none, |
| ); |
| expect( |
| copiedDetails, |
| ScrollableDetails( |
| direction: AxisDirection.left, |
| controller: controller, |
| physics: const ClampingScrollPhysics(), |
| decorationClipBehavior: Clip.none, |
| ), |
| ); |
| }); |
| |
| test('toString', (){ |
| const ScrollableDetails bareDetails = ScrollableDetails( |
| direction: AxisDirection.right, |
| ); |
| expect( |
| bareDetails.toString(), |
| equalsIgnoringHashCodes( |
| 'ScrollableDetails#00000(axisDirection: AxisDirection.right)' |
| ), |
| ); |
| final ScrollableDetails fullDetails = ScrollableDetails( |
| direction: AxisDirection.down, |
| controller: controller, |
| physics: const AlwaysScrollableScrollPhysics(), |
| decorationClipBehavior: Clip.hardEdge, |
| ); |
| expect( |
| fullDetails.toString(), |
| equalsIgnoringHashCodes( |
| 'ScrollableDetails#00000(' |
| 'axisDirection: AxisDirection.down, ' |
| 'scroll controller: ScrollController#00000(no clients), ' |
| 'scroll physics: AlwaysScrollableScrollPhysics, ' |
| 'decorationClipBehavior: Clip.hardEdge)' |
| ), |
| ); |
| }); |
| |
| test('deprecated clipBehavior is backwards compatible', (){ |
| const ScrollableDetails deprecatedClip = ScrollableDetails( |
| direction: AxisDirection.right, |
| clipBehavior: Clip.hardEdge, |
| ); |
| expect(deprecatedClip.clipBehavior, Clip.hardEdge); |
| expect(deprecatedClip.decorationClipBehavior, Clip.hardEdge); |
| |
| const ScrollableDetails newClip = ScrollableDetails( |
| direction: AxisDirection.right, |
| decorationClipBehavior: Clip.hardEdge, |
| ); |
| expect(newClip.clipBehavior, Clip.hardEdge); |
| expect(newClip.decorationClipBehavior, Clip.hardEdge); |
| }); |
| }); |
| |
| testWidgets("Keyboard scrolling doesn't happen if scroll physics are set to NeverScrollableScrollPhysics", (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.fuchsia), |
| home: CustomScrollView( |
| controller: controller, |
| physics: const NeverScrollableScrollPhysics(), |
| slivers: List<Widget>.generate( |
| 20, |
| (int index) { |
| return SliverToBoxAdapter( |
| child: Focus( |
| autofocus: index == 0, |
| child: SizedBox( |
| key: ValueKey<String>('Box $index'), |
| height: 50.0, |
| ), |
| ), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)), |
| ); |
| await tester.sendKeyDownEvent(modifierKey); |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); |
| await tester.sendKeyUpEvent(modifierKey); |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)), |
| ); |
| await tester.sendKeyDownEvent(modifierKey); |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); |
| await tester.sendKeyUpEvent(modifierKey); |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)), |
| ); |
| await tester.sendKeyEvent(LogicalKeyboardKey.pageDown); |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)), |
| ); |
| await tester.sendKeyEvent(LogicalKeyboardKey.pageUp); |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)), |
| ); |
| }, variant: KeySimulatorTransitModeVariant.all()); |
| |
| testWidgets('Vertical scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.fuchsia), |
| home: CustomScrollView( |
| controller: controller, |
| slivers: List<Widget>.generate( |
| 20, |
| (int index) { |
| return SliverToBoxAdapter( |
| child: Focus( |
| autofocus: index == 0, |
| child: SizedBox( |
| key: ValueKey<String>('Box $index'), |
| height: 50.0, |
| ), |
| ), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)), |
| ); |
| // We exclude the modifier keys here for web testing since default web shortcuts |
| // do not use a modifier key with arrow keys for ScrollActions. |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, -50.0, 800.0, 0.0)), |
| ); |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)), |
| ); |
| await tester.sendKeyEvent(LogicalKeyboardKey.pageDown); |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, -400.0, 800.0, -350.0)), |
| ); |
| await tester.sendKeyEvent(LogicalKeyboardKey.pageUp); |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)), |
| ); |
| }, variant: KeySimulatorTransitModeVariant.all()); |
| |
| testWidgets('Horizontal scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.fuchsia), |
| home: CustomScrollView( |
| controller: controller, |
| scrollDirection: Axis.horizontal, |
| slivers: List<Widget>.generate( |
| 20, |
| (int index) { |
| return SliverToBoxAdapter( |
| child: Focus( |
| autofocus: index == 0, |
| child: SizedBox( |
| key: ValueKey<String>('Box $index'), |
| width: 50.0, |
| ), |
| ), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 50.0, 600.0)), |
| ); |
| // We exclude the modifier keys here for web testing since default web shortcuts |
| // do not use a modifier key with arrow keys for ScrollActions. |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(-50.0, 0.0, 0.0, 600.0)), |
| ); |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 50.0, 600.0)), |
| ); |
| }, variant: KeySimulatorTransitModeVariant.all()); |
| |
| testWidgets('Horizontal scrollables are scrolled the correct direction in RTL locales.', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.fuchsia), |
| home: Directionality( |
| textDirection: TextDirection.rtl, |
| child: CustomScrollView( |
| controller: controller, |
| scrollDirection: Axis.horizontal, |
| slivers: List<Widget>.generate( |
| 20, |
| (int index) { |
| return SliverToBoxAdapter( |
| child: Focus( |
| autofocus: index == 0, |
| child: SizedBox( |
| key: ValueKey<String>('Box $index'), |
| width: 50.0, |
| ), |
| ), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(750.0, 0.0, 800.0, 600.0)), |
| ); |
| // We exclude the modifier keys here for web testing since default web shortcuts |
| // do not use a modifier key with arrow keys for ScrollActions. |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(800.0, 0.0, 850.0, 600.0)), |
| ); |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(750.0, 0.0, 800.0, 600.0)), |
| ); |
| }, variant: KeySimulatorTransitModeVariant.all()); |
| |
| testWidgets('Reversed vertical scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| final FocusNode focusNode = FocusNode(debugLabel: 'SizedBox'); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.fuchsia), |
| home: CustomScrollView( |
| controller: controller, |
| reverse: true, |
| slivers: List<Widget>.generate( |
| 20, |
| (int index) { |
| return SliverToBoxAdapter( |
| child: Focus( |
| focusNode: focusNode, |
| child: SizedBox( |
| key: ValueKey<String>('Box $index'), |
| height: 50.0, |
| ), |
| ), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| focusNode.requestFocus(); |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 550.0, 800.0, 600.0)), |
| ); |
| // We exclude the modifier keys here for web testing since default web shortcuts |
| // do not use a modifier key with arrow keys for ScrollActions. |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 600.0, 800.0, 650.0)), |
| ); |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 550.0, 800.0, 600.0)), |
| ); |
| await tester.sendKeyEvent(LogicalKeyboardKey.pageUp); |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 950.0, 800.0, 1000.0)), |
| ); |
| await tester.sendKeyEvent(LogicalKeyboardKey.pageDown); |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 550.0, 800.0, 600.0)), |
| ); |
| }, variant: KeySimulatorTransitModeVariant.all()); |
| |
| testWidgets('Reversed horizontal scrollables are scrolled when activated via keyboard.', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| final FocusNode focusNode = FocusNode(debugLabel: 'SizedBox'); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.fuchsia), |
| home: CustomScrollView( |
| controller: controller, |
| scrollDirection: Axis.horizontal, |
| reverse: true, |
| slivers: List<Widget>.generate( |
| 20, |
| (int index) { |
| return SliverToBoxAdapter( |
| child: Focus( |
| focusNode: focusNode, |
| child: SizedBox( |
| key: ValueKey<String>('Box $index'), |
| width: 50.0, |
| ), |
| ), |
| ); |
| }, |
| ), |
| ), |
| ), |
| ); |
| |
| focusNode.requestFocus(); |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(750.0, 0.0, 800.0, 600.00)), |
| ); |
| // We exclude the modifier keys here for web testing since default web shortcuts |
| // do not use a modifier key with arrow keys for ScrollActions. |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(800.0, 0.0, 850.0, 600.0)), |
| ); |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| }, variant: KeySimulatorTransitModeVariant.all()); |
| |
| testWidgets('Custom scrollables with a center sliver are scrolled when activated via keyboard.', (WidgetTester tester) async { |
| final ScrollController controller = ScrollController(); |
| final List<String> items = List<String>.generate(20, (int index) => 'Item $index'); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: ThemeData(platform: TargetPlatform.fuchsia), |
| home: CustomScrollView( |
| controller: controller, |
| center: const ValueKey<String>('Center'), |
| slivers: items.map<Widget>( |
| (String item) { |
| return SliverToBoxAdapter( |
| key: item == 'Item 10' ? const ValueKey<String>('Center') : null, |
| child: Focus( |
| autofocus: item == 'Item 10', |
| child: Container( |
| key: ValueKey<String>(item), |
| alignment: Alignment.center, |
| height: 100, |
| child: Text(item), |
| ), |
| ), |
| ); |
| }, |
| ).toList(), |
| ), |
| ), |
| ); |
| |
| await tester.pumpAndSettle(); |
| expect(controller.position.pixels, equals(0.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Item 10'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 100.0)), |
| ); |
| for (int i = 0; i < 10; ++i) { |
| // We exclude the modifier keys here for web testing since default web shortcuts |
| // do not use a modifier key with arrow keys for ScrollActions. |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| } |
| // Starts at #10 already, so doesn't work out to 500.0 because it hits bottom. |
| expect(controller.position.pixels, equals(400.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Item 10'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, -400.0, 800.0, -300.0)), |
| ); |
| for (int i = 0; i < 10; ++i) { |
| if (!kIsWeb) { |
| await tester.sendKeyDownEvent(modifierKey); |
| } |
| await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); |
| if (!kIsWeb) { |
| await tester.sendKeyUpEvent(modifierKey); |
| } |
| await tester.pumpAndSettle(); |
| } |
| // Goes up two past "center" where it started, so negative. |
| expect(controller.position.pixels, equals(-100.0)); |
| expect( |
| tester.getRect(find.byKey(const ValueKey<String>('Item 10'), skipOffstage: false)), |
| equals(const Rect.fromLTRB(0.0, 100.0, 800.0, 200.0)), |
| ); |
| }, variant: KeySimulatorTransitModeVariant.all()); |
| |
| testWidgets('Can scroll using intents only', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: ListView( |
| children: const <Widget>[ |
| SizedBox(height: 600.0, child: Text('The cow as white as milk')), |
| SizedBox(height: 600.0, child: Text('The cape as red as blood')), |
| SizedBox(height: 600.0, child: Text('The hair as yellow as corn')), |
| ], |
| ), |
| ), |
| ); |
| expect(find.text('The cow as white as milk'), findsOneWidget); |
| expect(find.text('The cape as red as blood'), findsNothing); |
| expect(find.text('The hair as yellow as corn'), findsNothing); |
| Actions.invoke(tester.element(find.byType(SliverList)), const ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page)); |
| await tester.pump(); // start scroll |
| await tester.pump(const Duration(milliseconds: 1000)); // end scroll |
| expect(find.text('The cow as white as milk'), findsOneWidget); |
| expect(find.text('The cape as red as blood'), findsOneWidget); |
| expect(find.text('The hair as yellow as corn'), findsNothing); |
| Actions.invoke(tester.element(find.byType(SliverList)), const ScrollIntent(direction: AxisDirection.down, type: ScrollIncrementType.page)); |
| await tester.pump(); // start scroll |
| await tester.pump(const Duration(milliseconds: 1000)); // end scroll |
| expect(find.text('The cow as white as milk'), findsNothing); |
| expect(find.text('The cape as red as blood'), findsOneWidget); |
| expect(find.text('The hair as yellow as corn'), findsOneWidget); |
| }); |
| } |