// 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() { 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.generate( 20, (int index) { return SliverToBoxAdapter( child: Focus( autofocus: index == 0, child: SizedBox( key: ValueKey('Box $index'), height: 50.0, ), ), ); }, ), ), ), ); await tester.pumpAndSettle(); expect(controller.position.pixels, equals(0.0)); expect( tester.getRect(find.byKey(const ValueKey('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('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('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('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('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.generate( 20, (int index) { return SliverToBoxAdapter( child: Focus( autofocus: index == 0, child: SizedBox( key: ValueKey('Box $index'), height: 50.0, ), ), ); }, ), ), ), ); await tester.pumpAndSettle(); expect(controller.position.pixels, equals(0.0)); expect( tester.getRect(find.byKey(const ValueKey('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('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('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('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('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.generate( 20, (int index) { return SliverToBoxAdapter( child: Focus( autofocus: index == 0, child: SizedBox( key: ValueKey('Box $index'), width: 50.0, ), ), ); }, ), ), ), ); await tester.pumpAndSettle(); expect(controller.position.pixels, equals(0.0)); expect( tester.getRect(find.byKey(const ValueKey('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('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('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.generate( 20, (int index) { return SliverToBoxAdapter( child: Focus( autofocus: index == 0, child: SizedBox( key: ValueKey('Box $index'), width: 50.0, ), ), ); }, ), ), ), ), ); await tester.pumpAndSettle(); expect(controller.position.pixels, equals(0.0)); expect( tester.getRect(find.byKey(const ValueKey('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('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('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.generate( 20, (int index) { return SliverToBoxAdapter( child: Focus( focusNode: focusNode, child: SizedBox( key: ValueKey('Box $index'), height: 50.0, ), ), ); }, ), ), ), ); focusNode.requestFocus(); await tester.pumpAndSettle(); expect(controller.position.pixels, equals(0.0)); expect( tester.getRect(find.byKey(const ValueKey('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('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('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('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('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.generate( 20, (int index) { return SliverToBoxAdapter( child: Focus( focusNode: focusNode, child: SizedBox( key: ValueKey('Box $index'), width: 50.0, ), ), ); }, ), ), ), ); focusNode.requestFocus(); await tester.pumpAndSettle(); expect(controller.position.pixels, equals(0.0)); expect( tester.getRect(find.byKey(const ValueKey('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('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 items = List.generate(20, (int index) => 'Item $index'); await tester.pumpWidget( MaterialApp( theme: ThemeData(platform: TargetPlatform.fuchsia), home: CustomScrollView( controller: controller, center: const ValueKey('Center'), slivers: items.map( (String item) { return SliverToBoxAdapter( key: item == 'Item 10' ? const ValueKey('Center') : null, child: Focus( autofocus: item == 'Item 10', child: Container( key: ValueKey(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('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('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('Item 10'), skipOffstage: false)), equals(const Rect.fromLTRB(0.0, 100.0, 800.0, 200.0)), ); }, variant: KeySimulatorTransitModeVariant.all()); }