// 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/src/gestures/monodrag.dart'; import 'package:flutter_test/flutter_test.dart'; import 'two_dimensional_utils.dart'; Widget? _testChildBuilder(BuildContext context, ChildVicinity vicinity) { return SizedBox( height: 200, width: 200, child: Center(child: Text('C${vicinity.xIndex}:R${vicinity.yIndex}')), ); } void main() { group('TwoDimensionalScrollView',() { testWidgets('asserts the axis directions do not conflict with one another', (WidgetTester tester) async { final List<Object> exceptions = <Object>[]; final FlutterExceptionHandler? oldHandler = FlutterError.onError; FlutterError.onError = (FlutterErrorDetails details) { exceptions.add(details.exception); }; // Horizontal wrong await tester.pumpWidget(MaterialApp( home: SimpleBuilderTableView( delegate: TwoDimensionalChildBuilderDelegate(builder: (_, __) => null), horizontalDetails: const ScrollableDetails.vertical(), // Horizontal has default const ScrollableDetails.horizontal() ), )); // Vertical wrong await tester.pumpWidget(MaterialApp( home: SimpleBuilderTableView( delegate: TwoDimensionalChildBuilderDelegate(builder: (_, __) => null), verticalDetails: const ScrollableDetails.horizontal(), // Horizontal has default const ScrollableDetails.horizontal() ), )); // Both wrong await tester.pumpWidget(MaterialApp( home: SimpleBuilderTableView( delegate: TwoDimensionalChildBuilderDelegate(builder: (_, __) => null), verticalDetails: const ScrollableDetails.horizontal(), horizontalDetails: const ScrollableDetails.vertical(), ), )); FlutterError.onError = oldHandler; expect(exceptions.length, 3); for (final Object exception in exceptions) { expect(exception, isAssertionError); expect((exception as AssertionError).message, contains('are not Axis')); } }, variant: TargetPlatformVariant.all()); testWidgets('ScrollableDetails.controller can set initial scroll positions, modify within bounds', (WidgetTester tester) async { final ScrollController verticalController = ScrollController(initialScrollOffset: 100); final ScrollController horizontalController = ScrollController(initialScrollOffset: 50); await tester.pumpWidget(MaterialApp( home: SimpleBuilderTableView( verticalDetails: ScrollableDetails.vertical(controller: verticalController), horizontalDetails: ScrollableDetails.horizontal(controller: horizontalController), delegate: TwoDimensionalChildBuilderDelegate( builder: _testChildBuilder, maxXIndex: 99, maxYIndex: 99, ), ), )); await tester.pumpAndSettle(); expect(verticalController.position.pixels, 100); expect(verticalController.position.maxScrollExtent, 19400); expect(horizontalController.position.pixels, 50); expect(horizontalController.position.maxScrollExtent, 19200); verticalController.jumpTo(verticalController.position.maxScrollExtent); horizontalController.jumpTo(horizontalController.position.maxScrollExtent); await tester.pump(); expect(verticalController.position.pixels, 19400); expect(horizontalController.position.pixels, 19200); // Out of bounds verticalController.jumpTo(verticalController.position.maxScrollExtent + 100); horizontalController.jumpTo(horizontalController.position.maxScrollExtent + 100); // Account for varying scroll physics for different platforms (overscroll) await tester.pumpAndSettle(); expect(verticalController.position.pixels, 19400); expect(horizontalController.position.pixels, 19200); }, variant: TargetPlatformVariant.all()); testWidgets('Properly assigns the PrimaryScrollController to the main axis on the correct platform', (WidgetTester tester) async { late ScrollController controller; Widget buildForPrimaryScrollController({ bool? explicitPrimary, Axis mainAxis = Axis.vertical, bool addControllerConflict = false, }) { return MaterialApp( home: PrimaryScrollController( controller: controller, child: SimpleBuilderTableView( mainAxis: mainAxis, primary: explicitPrimary, verticalDetails: ScrollableDetails.vertical( controller: addControllerConflict && mainAxis == Axis.vertical ? ScrollController() : null ), horizontalDetails: ScrollableDetails.horizontal( controller: addControllerConflict && mainAxis == Axis.horizontal ? ScrollController() : null ), delegate: TwoDimensionalChildBuilderDelegate( builder: _testChildBuilder, maxXIndex: 99, maxYIndex: 99, ), ), ), ); } // Horizontal default - horizontal never automatically adopts PSC controller = ScrollController(); await tester.pumpWidget(buildForPrimaryScrollController( mainAxis: Axis.horizontal, )); await tester.pumpAndSettle(); switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: expect(controller.hasClients, isFalse); } // Horizontal explicitly true controller = ScrollController(); await tester.pumpWidget(buildForPrimaryScrollController( mainAxis: Axis.horizontal, explicitPrimary: true, )); await tester.pumpAndSettle(); switch (defaultTargetPlatform) { // Primary explicitly true is always adopted. case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: expect(controller.hasClients, isTrue); expect(controller.position.axis, Axis.horizontal); } // Horizontal explicitly false controller = ScrollController(); await tester.pumpWidget(buildForPrimaryScrollController( mainAxis: Axis.horizontal, explicitPrimary: false, )); await tester.pumpAndSettle(); switch (defaultTargetPlatform) { // Primary explicitly false is never adopted. case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: expect(controller.hasClients, isFalse); } // Vertical default controller = ScrollController(); await tester.pumpWidget(buildForPrimaryScrollController()); await tester.pumpAndSettle(); switch (defaultTargetPlatform) { // Mobile platforms inherit the PSC without explicitly setting // primary case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: expect(controller.hasClients, isTrue); expect(controller.position.axis, Axis.vertical); case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: expect(controller.hasClients, isFalse); } // Vertical explicitly true controller = ScrollController(); await tester.pumpWidget(buildForPrimaryScrollController( explicitPrimary: true, )); await tester.pumpAndSettle(); switch (defaultTargetPlatform) { // Primary explicitly true is always adopted. case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: expect(controller.hasClients, isTrue); expect(controller.position.axis, Axis.vertical); } // Vertical explicitly false controller = ScrollController(); await tester.pumpWidget(buildForPrimaryScrollController( explicitPrimary: false, )); await tester.pumpAndSettle(); switch (defaultTargetPlatform) { // Primary explicitly false is never adopted. case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: expect(controller.hasClients, isFalse); } // Assertions final List<Object> exceptions = <Object>[]; final FlutterExceptionHandler? oldHandler = FlutterError.onError; FlutterError.onError = (FlutterErrorDetails details) { exceptions.add(details.exception); }; // Vertical asserts ScrollableDetails.controller has not been provided if // primary is explicitly set controller = ScrollController(); await tester.pumpWidget(buildForPrimaryScrollController( explicitPrimary: true, addControllerConflict: true, )); expect(exceptions.length, 1); expect(exceptions[0], isAssertionError); expect( (exceptions[0] as AssertionError).message, contains('TwoDimensionalScrollView.primary was explicitly set to true'), ); exceptions.clear(); // Horizontal asserts ScrollableDetails.controller has not been provided // if primary is explicitly set true controller = ScrollController(); await tester.pumpWidget(buildForPrimaryScrollController( mainAxis: Axis.horizontal, explicitPrimary: true, addControllerConflict: true, )); expect(exceptions.length, 1); expect(exceptions[0], isAssertionError); expect( (exceptions[0] as AssertionError).message, contains('TwoDimensionalScrollView.primary was explicitly set to true'), ); FlutterError.onError = oldHandler; }, variant: TargetPlatformVariant.all()); testWidgets('TwoDimensionalScrollable receives the correct details from TwoDimensionalScrollView', (WidgetTester tester) async { late BuildContext capturedContext; // Default await tester.pumpWidget(MaterialApp( home: SimpleBuilderTableView( delegate: TwoDimensionalChildBuilderDelegate( builder: (BuildContext context, ChildVicinity vicinity) { capturedContext = context; return Text(vicinity.toString()); }, ), ), )); await tester.pumpAndSettle(); TwoDimensionalScrollableState scrollable = TwoDimensionalScrollable.of( capturedContext, ); expect(scrollable.widget.verticalDetails.direction, AxisDirection.down); expect(scrollable.widget.horizontalDetails.direction, AxisDirection.right); expect(scrollable.widget.diagonalDragBehavior, DiagonalDragBehavior.none); expect(scrollable.widget.dragStartBehavior, DragStartBehavior.start); // Customized await tester.pumpWidget(MaterialApp( home: SimpleBuilderTableView( verticalDetails: const ScrollableDetails.vertical(reverse: true), horizontalDetails: const ScrollableDetails.horizontal(reverse: true), diagonalDragBehavior: DiagonalDragBehavior.weightedContinuous, dragStartBehavior: DragStartBehavior.down, delegate: TwoDimensionalChildBuilderDelegate( builder: _testChildBuilder, ), ), )); await tester.pumpAndSettle(); scrollable = TwoDimensionalScrollable.of(capturedContext); expect(scrollable.widget.verticalDetails.direction, AxisDirection.up); expect(scrollable.widget.horizontalDetails.direction, AxisDirection.left); expect(scrollable.widget.diagonalDragBehavior, DiagonalDragBehavior.weightedContinuous); expect(scrollable.widget.dragStartBehavior, DragStartBehavior.down); }, variant: TargetPlatformVariant.all()); }); }