mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1665 lines
54 KiB
Dart
1665 lines
54 KiB
Dart
// 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.
|
|
|
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
|
// machines.
|
|
@Tags(<String>['reduced-test-set'])
|
|
library;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../rendering/rendering_tester.dart' show TestClipPaintingContext;
|
|
import 'semantics_tester.dart';
|
|
import 'states.dart';
|
|
|
|
void main() {
|
|
// Regression test for https://github.com/flutter/flutter/issues/100451
|
|
testWidgets('PageView.builder respects findChildIndexCallback', (WidgetTester tester) async {
|
|
var finderCalled = false;
|
|
var itemCount = 7;
|
|
late StateSetter stateSetter;
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
stateSetter = setState;
|
|
return PageView.builder(
|
|
itemCount: itemCount,
|
|
itemBuilder: (BuildContext _, int index) =>
|
|
Container(key: Key('$index'), height: 2000.0),
|
|
findChildIndexCallback: (Key key) {
|
|
finderCalled = true;
|
|
return null;
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
expect(finderCalled, false);
|
|
|
|
// Trigger update.
|
|
stateSetter(() => itemCount = 77);
|
|
await tester.pump();
|
|
|
|
expect(finderCalled, true);
|
|
});
|
|
|
|
testWidgets('PageView resize from zero-size viewport should not lose state', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/88956
|
|
final controller = PageController(initialPage: 1);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget build(Size size) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox.fromSize(
|
|
size: size,
|
|
child: PageView(
|
|
controller: controller,
|
|
onPageChanged: (int page) {},
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// The pageView have a zero viewport, so nothing display.
|
|
await tester.pumpWidget(build(Size.zero));
|
|
expect(find.text('Alabama'), findsNothing);
|
|
expect(find.text('Alabama', skipOffstage: false), findsOneWidget);
|
|
|
|
// Resize from zero viewport to non-zero, the controller's initialPage 1 will display.
|
|
await tester.pumpWidget(build(const Size(200.0, 200.0)));
|
|
expect(find.text('Alaska'), findsOneWidget);
|
|
|
|
// Jump to page 'Iowa'.
|
|
controller.jumpToPage(kStates.indexOf('Iowa'));
|
|
await tester.pump();
|
|
expect(find.text('Iowa'), findsOneWidget);
|
|
|
|
// Resize to zero viewport again, nothing display.
|
|
await tester.pumpWidget(build(Size.zero));
|
|
expect(find.text('Iowa'), findsNothing);
|
|
|
|
// Resize from zero to non-zero, the pageView should not lose state, so the page 'Iowa' show again.
|
|
await tester.pumpWidget(build(const Size(200.0, 200.0)));
|
|
expect(find.text('Iowa'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Change the page through the controller when zero-size viewport', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/88956
|
|
final controller = PageController(initialPage: 1);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget build(Size size) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox.fromSize(
|
|
size: size,
|
|
child: PageView(
|
|
controller: controller,
|
|
onPageChanged: (int page) {},
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// The pageView have a zero viewport, so nothing display.
|
|
await tester.pumpWidget(build(Size.zero));
|
|
expect(find.text('Alabama'), findsNothing);
|
|
expect(find.text('Alabama', skipOffstage: false), findsOneWidget);
|
|
|
|
// Change the page through the page controller when zero viewport
|
|
controller.animateToPage(
|
|
kStates.indexOf('Iowa'),
|
|
duration: kTabScrollDuration,
|
|
curve: Curves.ease,
|
|
);
|
|
expect(controller.page, kStates.indexOf('Iowa'));
|
|
|
|
controller.jumpToPage(kStates.indexOf('Illinois'));
|
|
expect(controller.page, kStates.indexOf('Illinois'));
|
|
|
|
// Resize from zero viewport to non-zero, the latest state should not lost.
|
|
await tester.pumpWidget(build(const Size(200.0, 200.0)));
|
|
expect(controller.page, kStates.indexOf('Illinois'));
|
|
expect(find.text('Illinois'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('_PagePosition.applyViewportDimension should not throw', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/101007
|
|
final controller = PageController(initialPage: 1);
|
|
addTearDown(controller.dispose);
|
|
|
|
// Set the starting viewportDimension to 0.0
|
|
await tester.binding.setSurfaceSize(Size.zero);
|
|
final mediaQueryData = MediaQueryData.fromView(tester.view);
|
|
|
|
Widget build(Size size) {
|
|
return MediaQuery(
|
|
data: mediaQueryData.copyWith(size: size),
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox.expand(
|
|
child: PageView(
|
|
controller: controller,
|
|
onPageChanged: (int page) {},
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(build(Size.zero));
|
|
const surfaceSize = Size(500, 400);
|
|
await tester.binding.setSurfaceSize(surfaceSize);
|
|
await tester.pumpWidget(build(surfaceSize));
|
|
|
|
expect(tester.takeException(), isNull);
|
|
|
|
// Reset TestWidgetsFlutterBinding surfaceSize
|
|
await tester.binding.setSurfaceSize(null);
|
|
});
|
|
|
|
testWidgets('PageController cannot return page while unattached', (WidgetTester tester) async {
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
expect(() => controller.page, throwsAssertionError);
|
|
});
|
|
|
|
testWidgets('PageView control test', (WidgetTester tester) async {
|
|
final log = <String>[];
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
children: kStates.map<Widget>((String state) {
|
|
return GestureDetector(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
onTap: () {
|
|
log.add(state);
|
|
},
|
|
child: Container(height: 200.0, color: const Color(0xFF0000FF), child: Text(state)),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('Alabama'));
|
|
expect(log, equals(<String>['Alabama']));
|
|
log.clear();
|
|
|
|
expect(find.text('Alaska'), findsNothing);
|
|
|
|
await tester.drag(find.byType(PageView), const Offset(-20.0, 0.0));
|
|
await tester.pump();
|
|
|
|
expect(find.text('Alabama'), findsOneWidget);
|
|
expect(find.text('Alaska'), findsOneWidget);
|
|
expect(find.text('Arizona'), findsNothing);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Alabama'), findsOneWidget);
|
|
expect(find.text('Alaska'), findsNothing);
|
|
|
|
await tester.drag(find.byType(PageView), const Offset(-401.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Alabama'), findsNothing);
|
|
expect(find.text('Alaska'), findsOneWidget);
|
|
expect(find.text('Arizona'), findsNothing);
|
|
|
|
await tester.tap(find.text('Alaska'));
|
|
expect(log, equals(<String>['Alaska']));
|
|
log.clear();
|
|
|
|
await tester.fling(find.byType(PageView), const Offset(-200.0, 0.0), 1000.0);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Alabama'), findsNothing);
|
|
expect(find.text('Alaska'), findsNothing);
|
|
expect(find.text('Arizona'), findsOneWidget);
|
|
|
|
await tester.fling(find.byType(PageView), const Offset(200.0, 0.0), 1000.0);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Alabama'), findsNothing);
|
|
expect(find.text('Alaska'), findsOneWidget);
|
|
expect(find.text('Arizona'), findsNothing);
|
|
});
|
|
|
|
testWidgets(
|
|
'PageView does not squish when overscrolled',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: PageView(
|
|
children: List<Widget>.generate(10, (int i) {
|
|
return Container(key: ValueKey<int>(i), color: const Color(0xFF0000FF));
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
|
|
Size sizeOf(int i) => tester.getSize(find.byKey(ValueKey<int>(i)));
|
|
double leftOf(int i) => tester.getTopLeft(find.byKey(ValueKey<int>(i))).dx;
|
|
|
|
expect(leftOf(0), equals(0.0));
|
|
expect(sizeOf(0), equals(const Size(800.0, 600.0)));
|
|
|
|
// Going into overscroll.
|
|
await tester.drag(find.byType(PageView), const Offset(100.0, 0.0));
|
|
await tester.pump();
|
|
|
|
expect(leftOf(0), greaterThan(0.0));
|
|
expect(sizeOf(0), equals(const Size(800.0, 600.0)));
|
|
|
|
// Easing overscroll past overscroll limit.
|
|
if (debugDefaultTargetPlatformOverride == TargetPlatform.macOS) {
|
|
await tester.drag(find.byType(PageView), const Offset(-500.0, 0.0));
|
|
} else {
|
|
await tester.drag(find.byType(PageView), const Offset(-200.0, 0.0));
|
|
}
|
|
await tester.pump();
|
|
|
|
expect(leftOf(0), lessThan(0.0));
|
|
expect(sizeOf(0), equals(const Size(800.0, 600.0)));
|
|
},
|
|
variant: const TargetPlatformVariant(<TargetPlatform>{
|
|
TargetPlatform.iOS,
|
|
TargetPlatform.macOS,
|
|
}),
|
|
);
|
|
|
|
testWidgets('PageController control test', (WidgetTester tester) async {
|
|
final controller = PageController(initialPage: 4);
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: 600.0,
|
|
height: 400.0,
|
|
child: PageView(
|
|
controller: controller,
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('California'), findsOneWidget);
|
|
|
|
controller.nextPage(duration: const Duration(milliseconds: 150), curve: Curves.ease);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Colorado'), findsOneWidget);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: 300.0,
|
|
height: 400.0,
|
|
child: PageView(
|
|
controller: controller,
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Colorado'), findsOneWidget);
|
|
|
|
controller.previousPage(duration: const Duration(milliseconds: 150), curve: Curves.ease);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('California'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('PageController page stability', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: 600.0,
|
|
height: 400.0,
|
|
child: PageView(children: kStates.map<Widget>((String state) => Text(state)).toList()),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Alabama'), findsOneWidget);
|
|
|
|
await tester.drag(find.byType(PageView), const Offset(-1250.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Arizona'), findsOneWidget);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: 250.0,
|
|
height: 100.0,
|
|
child: PageView(children: kStates.map<Widget>((String state) => Text(state)).toList()),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Arizona'), findsOneWidget);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: 450.0,
|
|
height: 400.0,
|
|
child: PageView(children: kStates.map<Widget>((String state) => Text(state)).toList()),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Arizona'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('PageController nextPage and previousPage return Futures that resolve', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(
|
|
controller: controller,
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
var nextPageCompleted = false;
|
|
controller
|
|
.nextPage(duration: const Duration(milliseconds: 150), curve: Curves.ease)
|
|
.then((_) => nextPageCompleted = true);
|
|
|
|
expect(nextPageCompleted, false);
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
expect(nextPageCompleted, false);
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
expect(nextPageCompleted, true);
|
|
|
|
var previousPageCompleted = false;
|
|
controller
|
|
.previousPage(duration: const Duration(milliseconds: 150), curve: Curves.ease)
|
|
.then((_) => previousPageCompleted = true);
|
|
|
|
expect(previousPageCompleted, false);
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
expect(previousPageCompleted, false);
|
|
await tester.pump(const Duration(milliseconds: 200));
|
|
expect(previousPageCompleted, true);
|
|
});
|
|
|
|
testWidgets('PageView in zero-size container', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox.shrink(
|
|
child: PageView(children: kStates.map<Widget>((String state) => Text(state)).toList()),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Alabama', skipOffstage: false), findsOneWidget);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: 200.0,
|
|
height: 200.0,
|
|
child: PageView(children: kStates.map<Widget>((String state) => Text(state)).toList()),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Alabama'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Page changes at halfway point', (WidgetTester tester) async {
|
|
final log = <int>[];
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(
|
|
onPageChanged: log.add,
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(log, isEmpty);
|
|
|
|
final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
|
|
// The page view is 800.0 wide, so this move is just short of halfway.
|
|
await gesture.moveBy(const Offset(-380.0, 0.0));
|
|
|
|
expect(log, isEmpty);
|
|
|
|
// We've crossed the halfway mark.
|
|
await gesture.moveBy(const Offset(-40.0, 0.0));
|
|
|
|
expect(log, equals(const <int>[1]));
|
|
log.clear();
|
|
|
|
// Moving a bit more should not generate redundant notifications.
|
|
await gesture.moveBy(const Offset(-40.0, 0.0));
|
|
|
|
expect(log, isEmpty);
|
|
|
|
await gesture.moveBy(const Offset(-40.0, 0.0));
|
|
await tester.pump();
|
|
|
|
await gesture.moveBy(const Offset(-40.0, 0.0));
|
|
await tester.pump();
|
|
|
|
await gesture.moveBy(const Offset(-40.0, 0.0));
|
|
await tester.pump();
|
|
|
|
expect(log, isEmpty);
|
|
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(log, isEmpty);
|
|
|
|
expect(find.text('Alabama'), findsNothing);
|
|
expect(find.text('Alaska'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Bouncing scroll physics ballistics does not overshoot', (WidgetTester tester) async {
|
|
final log = <int>[];
|
|
final controller = PageController(viewportFraction: 0.9);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget build(PageController controller, {Size? size}) {
|
|
final Widget pageView = Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(
|
|
controller: controller,
|
|
onPageChanged: log.add,
|
|
physics: const BouncingScrollPhysics(),
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
),
|
|
);
|
|
|
|
if (size != null) {
|
|
return OverflowBox(
|
|
minWidth: size.width,
|
|
minHeight: size.height,
|
|
maxWidth: size.width,
|
|
maxHeight: size.height,
|
|
child: pageView,
|
|
);
|
|
} else {
|
|
return pageView;
|
|
}
|
|
}
|
|
|
|
await tester.pumpWidget(build(controller));
|
|
expect(log, isEmpty);
|
|
|
|
// Fling right to move to a non-existent page at the beginning of the
|
|
// PageView, and confirm that the PageView settles back on the first page.
|
|
await tester.fling(find.byType(PageView), const Offset(100.0, 0.0), 800.0);
|
|
await tester.pumpAndSettle();
|
|
expect(log, isEmpty);
|
|
|
|
expect(find.text('Alabama'), findsOneWidget);
|
|
expect(find.text('Alaska'), findsOneWidget);
|
|
expect(find.text('Arizona'), findsNothing);
|
|
|
|
// Try again with a Cupertino "Plus" device size.
|
|
await tester.pumpWidget(build(controller, size: const Size(414.0, 736.0)));
|
|
expect(log, isEmpty);
|
|
|
|
await tester.fling(find.byType(PageView), const Offset(100.0, 0.0), 800.0);
|
|
await tester.pumpAndSettle();
|
|
expect(log, isEmpty);
|
|
|
|
expect(find.text('Alabama'), findsOneWidget);
|
|
expect(find.text('Alaska'), findsOneWidget);
|
|
expect(find.text('Arizona'), findsNothing);
|
|
});
|
|
|
|
testWidgets('PageView viewportFraction', (WidgetTester tester) async {
|
|
var controller = PageController(viewportFraction: 7 / 8);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget build(PageController controller) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView.builder(
|
|
controller: controller,
|
|
itemCount: kStates.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 200.0,
|
|
color: index.isEven ? const Color(0xFF0000FF) : const Color(0xFF00FF00),
|
|
child: Text(kStates[index]),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(build(controller));
|
|
|
|
expect(tester.getTopLeft(find.text('Alabama')), const Offset(50.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Alaska')), const Offset(750.0, 0.0));
|
|
|
|
controller.jumpToPage(10);
|
|
await tester.pump();
|
|
|
|
expect(tester.getTopLeft(find.text('Georgia')), const Offset(-650.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Hawaii')), const Offset(50.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Idaho')), const Offset(750.0, 0.0));
|
|
|
|
controller = PageController(viewportFraction: 39 / 40);
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(build(controller));
|
|
|
|
expect(tester.getTopLeft(find.text('Georgia')), const Offset(-770.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Hawaii')), const Offset(10.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Idaho')), const Offset(790.0, 0.0));
|
|
});
|
|
|
|
testWidgets('Page snapping disable and reenable', (WidgetTester tester) async {
|
|
final log = <int>[];
|
|
|
|
Widget build({required bool pageSnapping}) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(
|
|
pageSnapping: pageSnapping,
|
|
onPageChanged: log.add,
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(build(pageSnapping: true));
|
|
expect(log, isEmpty);
|
|
|
|
// Drag more than halfway to the next page, to confirm the default behavior.
|
|
TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
|
|
// The page view is 800.0 wide, so this move is just beyond halfway.
|
|
await gesture.moveBy(const Offset(-420.0, 0.0));
|
|
|
|
expect(log, equals(const <int>[1]));
|
|
log.clear();
|
|
|
|
// Release the gesture, confirm that the page settles on the next.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Alabama'), findsNothing);
|
|
expect(find.text('Alaska'), findsOneWidget);
|
|
|
|
// Disable page snapping, and try moving halfway. Confirm it doesn't snap.
|
|
await tester.pumpWidget(build(pageSnapping: false));
|
|
gesture = await tester.startGesture(const Offset(100.0, 100.0));
|
|
// Move just beyond halfway, again.
|
|
await gesture.moveBy(const Offset(-420.0, 0.0));
|
|
|
|
// Page notifications still get sent.
|
|
expect(log, equals(const <int>[2]));
|
|
log.clear();
|
|
|
|
// Release the gesture, confirm that both pages are visible.
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Alabama'), findsNothing);
|
|
expect(find.text('Alaska'), findsOneWidget);
|
|
expect(find.text('Arizona'), findsOneWidget);
|
|
expect(find.text('Arkansas'), findsNothing);
|
|
|
|
// Now re-enable snapping, confirm that we've settled on a page.
|
|
await tester.pumpWidget(build(pageSnapping: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(log, isEmpty);
|
|
|
|
expect(find.text('Alaska'), findsNothing);
|
|
expect(find.text('Arizona'), findsOneWidget);
|
|
expect(find.text('Arkansas'), findsNothing);
|
|
});
|
|
|
|
testWidgets('PageView small viewportFraction', (WidgetTester tester) async {
|
|
final controller = PageController(viewportFraction: 1 / 8);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget build(PageController controller) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView.builder(
|
|
controller: controller,
|
|
itemCount: kStates.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 200.0,
|
|
color: index.isEven ? const Color(0xFF0000FF) : const Color(0xFF00FF00),
|
|
child: Text(kStates[index]),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(build(controller));
|
|
|
|
expect(tester.getTopLeft(find.text('Alabama')), const Offset(350.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Alaska')), const Offset(450.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Arizona')), const Offset(550.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Arkansas')), const Offset(650.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('California')), const Offset(750.0, 0.0));
|
|
|
|
controller.jumpToPage(10);
|
|
await tester.pump();
|
|
|
|
expect(tester.getTopLeft(find.text('Connecticut')), const Offset(-50.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Delaware')), const Offset(50.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Florida')), const Offset(150.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Georgia')), const Offset(250.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Hawaii')), const Offset(350.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Idaho')), const Offset(450.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Illinois')), const Offset(550.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Indiana')), const Offset(650.0, 0.0));
|
|
expect(tester.getTopLeft(find.text('Iowa')), const Offset(750.0, 0.0));
|
|
});
|
|
|
|
testWidgets('PageView large viewportFraction', (WidgetTester tester) async {
|
|
final controller = PageController(viewportFraction: 5 / 4);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget build(PageController controller) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView.builder(
|
|
controller: controller,
|
|
itemCount: kStates.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 200.0,
|
|
color: index.isEven ? const Color(0xFF0000FF) : const Color(0xFF00FF00),
|
|
child: Text(kStates[index]),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(build(controller));
|
|
|
|
expect(tester.getTopLeft(find.text('Alabama')), const Offset(-100.0, 0.0));
|
|
expect(tester.getBottomRight(find.text('Alabama')), const Offset(900.0, 600.0));
|
|
|
|
controller.jumpToPage(10);
|
|
await tester.pump();
|
|
|
|
expect(tester.getTopLeft(find.text('Hawaii')), const Offset(-100.0, 0.0));
|
|
});
|
|
|
|
testWidgets('Updating PageView large viewportFraction', (WidgetTester tester) async {
|
|
Widget build(PageController controller) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView.builder(
|
|
controller: controller,
|
|
itemCount: kStates.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 200.0,
|
|
color: index.isEven ? const Color(0xFF0000FF) : const Color(0xFF00FF00),
|
|
child: Text(kStates[index]),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
final oldController = PageController(viewportFraction: 5 / 4);
|
|
addTearDown(oldController.dispose);
|
|
await tester.pumpWidget(build(oldController));
|
|
|
|
expect(tester.getTopLeft(find.text('Alabama')), const Offset(-100, 0));
|
|
expect(tester.getBottomRight(find.text('Alabama')), const Offset(900.0, 600.0));
|
|
|
|
final newController = PageController(viewportFraction: 4);
|
|
addTearDown(newController.dispose);
|
|
await tester.pumpWidget(build(newController));
|
|
newController.jumpToPage(10);
|
|
await tester.pump();
|
|
|
|
expect(tester.getTopLeft(find.text('Hawaii')), const Offset(-(4 - 1) * 800 / 2, 0));
|
|
});
|
|
|
|
testWidgets('PageView large viewportFraction can scroll to the last page and snap', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/45096.
|
|
final controller = PageController(viewportFraction: 5 / 4);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget build(PageController controller) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView.builder(
|
|
controller: controller,
|
|
itemCount: 3,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 200.0,
|
|
color: index.isEven ? const Color(0xFF0000FF) : const Color(0xFF00FF00),
|
|
child: Text(index.toString()),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(build(controller));
|
|
|
|
expect(tester.getCenter(find.text('0')), const Offset(400, 300));
|
|
|
|
controller.jumpToPage(2);
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.getCenter(find.text('2')), const Offset(400, 300));
|
|
});
|
|
|
|
testWidgets('All visible pages are able to receive touch events', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/23873.
|
|
final controller = PageController(viewportFraction: 1 / 4);
|
|
addTearDown(controller.dispose);
|
|
late int tappedIndex;
|
|
|
|
Widget build() {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView.builder(
|
|
controller: controller,
|
|
itemCount: 20,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return GestureDetector(
|
|
onTap: () => tappedIndex = index,
|
|
child: SizedBox.expand(child: Text('$index')),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
Iterable<int> visiblePages = const <int>[0, 1, 2];
|
|
await tester.pumpWidget(build());
|
|
|
|
// The first 3 items should be visible and tappable.
|
|
for (final index in visiblePages) {
|
|
expect(find.text(index.toString()), findsOneWidget);
|
|
// The center of page 2's x-coordinate is 800, so we have to manually
|
|
// offset it a bit to make sure the tap lands within the screen.
|
|
final Offset center = tester.getCenter(find.text('$index')) - const Offset(3, 0);
|
|
await tester.tapAt(center);
|
|
expect(tappedIndex, index);
|
|
}
|
|
|
|
controller.jumpToPage(19);
|
|
await tester.pump();
|
|
// The last 3 items should be visible and tappable.
|
|
visiblePages = const <int>[17, 18, 19];
|
|
for (final index in visiblePages) {
|
|
expect(find.text('$index'), findsOneWidget);
|
|
await tester.tap(find.text('$index'));
|
|
expect(tappedIndex, index);
|
|
}
|
|
});
|
|
|
|
testWidgets('the current item remains centered on constraint change', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/50505.
|
|
final controller = PageController(initialPage: kStates.length - 1, viewportFraction: 0.5);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget build(Size size) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox.fromSize(
|
|
size: size,
|
|
child: PageView(
|
|
controller: controller,
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
onPageChanged: (int page) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Verifies that the last item is centered on screen.
|
|
void verifyCentered() {
|
|
expect(
|
|
tester.getCenter(find.text(kStates.last)),
|
|
offsetMoreOrLessEquals(const Offset(400, 300)),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(build(const Size(300, 300)));
|
|
await tester.pumpAndSettle();
|
|
|
|
verifyCentered();
|
|
|
|
await tester.pumpWidget(build(const Size(200, 300)));
|
|
await tester.pumpAndSettle();
|
|
|
|
verifyCentered();
|
|
});
|
|
|
|
testWidgets('PageView does not report page changed on overscroll', (WidgetTester tester) async {
|
|
final controller = PageController(initialPage: kStates.length - 1);
|
|
addTearDown(controller.dispose);
|
|
var changeIndex = 0;
|
|
Widget build() {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(
|
|
controller: controller,
|
|
children: kStates.map<Widget>((String state) => Text(state)).toList(),
|
|
onPageChanged: (int page) {
|
|
changeIndex = page;
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(build());
|
|
controller.jumpToPage(kStates.length * 2); // try to move beyond max range
|
|
// change index should be zero, shouldn't fire onPageChanged
|
|
expect(changeIndex, 0);
|
|
await tester.pump();
|
|
expect(changeIndex, 0);
|
|
});
|
|
|
|
testWidgets('PageView can restore page', (WidgetTester tester) async {
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
expect(
|
|
() => controller.page,
|
|
throwsA(
|
|
isAssertionError.having(
|
|
(AssertionError error) => error.message,
|
|
'message',
|
|
equals('PageController.page cannot be accessed before a PageView is built with it.'),
|
|
),
|
|
),
|
|
);
|
|
final bucket = PageStorageBucket();
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageStorage(
|
|
bucket: bucket,
|
|
child: PageView(
|
|
key: const PageStorageKey<String>('PageView'),
|
|
controller: controller,
|
|
children: const <Widget>[Placeholder(), Placeholder(), Placeholder()],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(controller.page, 0);
|
|
controller.jumpToPage(2);
|
|
expect(await tester.pumpAndSettle(const Duration(minutes: 1)), 2);
|
|
expect(controller.page, 2);
|
|
await tester.pumpWidget(PageStorage(bucket: bucket, child: Container()));
|
|
expect(
|
|
() => controller.page,
|
|
throwsA(
|
|
isAssertionError.having(
|
|
(AssertionError error) => error.message,
|
|
'message',
|
|
equals('PageController.page cannot be accessed before a PageView is built with it.'),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageStorage(
|
|
bucket: bucket,
|
|
child: PageView(
|
|
key: const PageStorageKey<String>('PageView'),
|
|
controller: controller,
|
|
children: const <Widget>[Placeholder(), Placeholder(), Placeholder()],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(controller.page, 2);
|
|
|
|
final controller2 = PageController(keepPage: false);
|
|
addTearDown(controller2.dispose);
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageStorage(
|
|
bucket: bucket,
|
|
child: PageView(
|
|
key: const PageStorageKey<String>(
|
|
'Check it again against your list and see consistency!',
|
|
),
|
|
controller: controller2,
|
|
children: const <Widget>[Placeholder(), Placeholder(), Placeholder()],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(controller2.page, 0);
|
|
});
|
|
|
|
testWidgets('PageView exposes semantics of children', (WidgetTester tester) async {
|
|
final semantics = SemanticsTester(tester);
|
|
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(
|
|
controller: controller,
|
|
children: List<Widget>.generate(3, (int i) {
|
|
return Semantics(container: true, child: Text('Page #$i'));
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
expect(controller.page, 0);
|
|
|
|
expect(semantics, includesNodeWith(label: 'Page #0'));
|
|
expect(semantics, isNot(includesNodeWith(label: 'Page #1')));
|
|
expect(semantics, isNot(includesNodeWith(label: 'Page #2')));
|
|
|
|
controller.jumpToPage(1);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(semantics, isNot(includesNodeWith(label: 'Page #0')));
|
|
expect(semantics, includesNodeWith(label: 'Page #1'));
|
|
expect(semantics, isNot(includesNodeWith(label: 'Page #2')));
|
|
|
|
controller.jumpToPage(2);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(semantics, isNot(includesNodeWith(label: 'Page #0')));
|
|
expect(semantics, isNot(includesNodeWith(label: 'Page #1')));
|
|
expect(semantics, includesNodeWith(label: 'Page #2'));
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('PageMetrics', (WidgetTester tester) async {
|
|
final page = PageMetrics(
|
|
minScrollExtent: 100.0,
|
|
maxScrollExtent: 200.0,
|
|
pixels: 150.0,
|
|
viewportDimension: 25.0,
|
|
axisDirection: AxisDirection.right,
|
|
viewportFraction: 1.0,
|
|
devicePixelRatio: tester.view.devicePixelRatio,
|
|
);
|
|
expect(page.page, 6);
|
|
final PageMetrics page2 = page.copyWith(pixels: page.pixels - 100.0);
|
|
expect(page2.page, 4.0);
|
|
});
|
|
|
|
testWidgets('Page controller can handle rounding issue', (WidgetTester tester) async {
|
|
final pageController = PageController();
|
|
addTearDown(pageController.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(
|
|
controller: pageController,
|
|
children: List<Widget>.generate(3, (int i) {
|
|
return Semantics(container: true, child: Text('Page #$i'));
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
// Simulate precision error.
|
|
pageController.position.jumpTo(799.99999999999);
|
|
expect(pageController.page, 1);
|
|
});
|
|
|
|
testWidgets('PageView can participate in a11y scrolling', (WidgetTester tester) async {
|
|
final semantics = SemanticsTester(tester);
|
|
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(
|
|
controller: controller,
|
|
allowImplicitScrolling: true,
|
|
children: List<Widget>.generate(4, (int i) {
|
|
return Semantics(container: true, child: Text('Page #$i'));
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
expect(controller.page, 0);
|
|
|
|
expect(semantics, includesNodeWith(flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling]));
|
|
expect(semantics, includesNodeWith(label: 'Page #0'));
|
|
expect(
|
|
semantics,
|
|
includesNodeWith(label: 'Page #1', flags: <SemanticsFlag>[SemanticsFlag.isHidden]),
|
|
);
|
|
expect(
|
|
semantics,
|
|
isNot(includesNodeWith(label: 'Page #2', flags: <SemanticsFlag>[SemanticsFlag.isHidden])),
|
|
);
|
|
expect(
|
|
semantics,
|
|
isNot(includesNodeWith(label: 'Page #3', flags: <SemanticsFlag>[SemanticsFlag.isHidden])),
|
|
);
|
|
|
|
controller.nextPage(duration: const Duration(milliseconds: 150), curve: Curves.ease);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
semantics,
|
|
includesNodeWith(label: 'Page #0', flags: <SemanticsFlag>[SemanticsFlag.isHidden]),
|
|
);
|
|
expect(semantics, includesNodeWith(label: 'Page #1'));
|
|
expect(
|
|
semantics,
|
|
includesNodeWith(label: 'Page #2', flags: <SemanticsFlag>[SemanticsFlag.isHidden]),
|
|
);
|
|
expect(
|
|
semantics,
|
|
isNot(includesNodeWith(label: 'Page #3', flags: <SemanticsFlag>[SemanticsFlag.isHidden])),
|
|
);
|
|
|
|
controller.nextPage(duration: const Duration(milliseconds: 150), curve: Curves.ease);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
semantics,
|
|
isNot(includesNodeWith(label: 'Page #0', flags: <SemanticsFlag>[SemanticsFlag.isHidden])),
|
|
);
|
|
expect(
|
|
semantics,
|
|
includesNodeWith(label: 'Page #1', flags: <SemanticsFlag>[SemanticsFlag.isHidden]),
|
|
);
|
|
expect(semantics, includesNodeWith(label: 'Page #2'));
|
|
expect(
|
|
semantics,
|
|
includesNodeWith(label: 'Page #3', flags: <SemanticsFlag>[SemanticsFlag.isHidden]),
|
|
);
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('PageView respects clipBehavior', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(children: <Widget>[Container(height: 2000.0)]),
|
|
),
|
|
);
|
|
|
|
// 1st, check that the render object has received the default clip behavior.
|
|
final RenderViewport renderObject = tester.allRenderObjects.whereType<RenderViewport>().first;
|
|
expect(renderObject.clipBehavior, equals(Clip.hardEdge));
|
|
|
|
// 2nd, check that the painting context has received the default clip behavior.
|
|
final context = TestClipPaintingContext();
|
|
renderObject.paint(context, Offset.zero);
|
|
expect(context.clipBehavior, equals(Clip.hardEdge));
|
|
|
|
// 3rd, pump a new widget to check that the render object can update its clip behavior.
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView(
|
|
clipBehavior: Clip.antiAlias,
|
|
children: <Widget>[Container(height: 2000.0)],
|
|
),
|
|
),
|
|
);
|
|
expect(renderObject.clipBehavior, equals(Clip.antiAlias));
|
|
|
|
// 4th, check that a non-default clip behavior can be sent to the painting context.
|
|
renderObject.paint(context, Offset.zero);
|
|
expect(context.clipBehavior, equals(Clip.antiAlias));
|
|
});
|
|
|
|
testWidgets('PageView.padEnds tests', (WidgetTester tester) async {
|
|
Finder viewportFinder() => find.byType(SliverFillViewport, skipOffstage: false);
|
|
|
|
// PageView() defaults to true.
|
|
await tester.pumpWidget(Directionality(textDirection: TextDirection.ltr, child: PageView()));
|
|
|
|
expect(tester.widget<SliverFillViewport>(viewportFinder()).padEnds, true);
|
|
|
|
// PageView(padEnds: false) is propagated properly.
|
|
await tester.pumpWidget(
|
|
Directionality(textDirection: TextDirection.ltr, child: PageView(padEnds: false)),
|
|
);
|
|
|
|
expect(tester.widget<SliverFillViewport>(viewportFinder()).padEnds, false);
|
|
});
|
|
|
|
testWidgets('PageView - precision error inside RenderSliverFixedExtentBoxAdaptor', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/95101
|
|
final controller = PageController(initialPage: 152);
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
Center(
|
|
child: SizedBox(
|
|
width: 392.72727272727275,
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PageView.builder(
|
|
controller: controller,
|
|
itemCount: 366,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return const SizedBox();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
controller.jumpToPage(365);
|
|
await tester.pump();
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('PageView content should not be stretched on precision error', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/126561.
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
|
|
const pixel6EmulatorWidth = 411.42857142857144;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
width: pixel6EmulatorWidth,
|
|
child: PageView(
|
|
controller: controller,
|
|
physics: const PageScrollPhysics().applyTo(const ClampingScrollPhysics()),
|
|
children: const <Widget>[
|
|
Center(child: Text('First Page')),
|
|
Center(child: Text('Second Page')),
|
|
Center(child: Text('Third Page')),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
controller.animateToPage(2, duration: const Duration(milliseconds: 300), curve: Curves.ease);
|
|
await tester.pumpAndSettle();
|
|
|
|
await expectLater(
|
|
find.byType(PageView),
|
|
matchesGoldenFile('page_view_no_stretch_precision_error.png'),
|
|
);
|
|
});
|
|
|
|
testWidgets('PageController onAttach, onDetach', (WidgetTester tester) async {
|
|
var attach = 0;
|
|
var detach = 0;
|
|
final controller = PageController(
|
|
onAttach: (_) {
|
|
attach++;
|
|
},
|
|
onDetach: (_) {
|
|
detach++;
|
|
},
|
|
);
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: PageView(
|
|
controller: controller,
|
|
physics: const PageScrollPhysics().applyTo(const ClampingScrollPhysics()),
|
|
children: const <Widget>[
|
|
Center(child: Text('First Page')),
|
|
Center(child: Text('Second Page')),
|
|
Center(child: Text('Third Page')),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(attach, 1);
|
|
expect(detach, 0);
|
|
|
|
await tester.pumpWidget(Container());
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(attach, 1);
|
|
expect(detach, 1);
|
|
});
|
|
|
|
group('$PageView handles change of controller', () {
|
|
final GlobalKey key = GlobalKey();
|
|
|
|
Widget createPageView(PageController? controller) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: PageView(
|
|
key: key,
|
|
controller: controller,
|
|
children: const <Widget>[
|
|
Center(child: Text('0')),
|
|
Center(child: Text('1')),
|
|
Center(child: Text('2')),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> testPageViewWithController(
|
|
PageController controller,
|
|
WidgetTester tester,
|
|
bool controls,
|
|
) async {
|
|
int currentVisiblePage() {
|
|
return int.parse(tester.widgetList(find.byType(Text)).whereType<Text>().first.data!);
|
|
}
|
|
|
|
final int initialPageInView = currentVisiblePage();
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
if (controls) {
|
|
controller.jumpToPage(i);
|
|
await tester.pumpAndSettle();
|
|
expect(currentVisiblePage(), i);
|
|
} else {
|
|
expect(() => controller.jumpToPage(i), throwsAssertionError);
|
|
expect(currentVisiblePage(), initialPageInView);
|
|
}
|
|
}
|
|
}
|
|
|
|
testWidgets('null to value', (WidgetTester tester) async {
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(createPageView(null));
|
|
await tester.pumpWidget(createPageView(controller));
|
|
await testPageViewWithController(controller, tester, true);
|
|
});
|
|
|
|
testWidgets('value to value', (WidgetTester tester) async {
|
|
final controller1 = PageController();
|
|
addTearDown(controller1.dispose);
|
|
final controller2 = PageController();
|
|
addTearDown(controller2.dispose);
|
|
await tester.pumpWidget(createPageView(controller1));
|
|
await testPageViewWithController(controller1, tester, true);
|
|
await tester.pumpWidget(createPageView(controller2));
|
|
await testPageViewWithController(controller1, tester, false);
|
|
await testPageViewWithController(controller2, tester, true);
|
|
});
|
|
|
|
testWidgets('value to null', (WidgetTester tester) async {
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(createPageView(controller));
|
|
await testPageViewWithController(controller, tester, true);
|
|
await tester.pumpWidget(createPageView(null));
|
|
await testPageViewWithController(controller, tester, false);
|
|
});
|
|
|
|
testWidgets('null to null', (WidgetTester tester) async {
|
|
await tester.pumpWidget(createPageView(null));
|
|
await tester.pumpWidget(createPageView(null));
|
|
});
|
|
});
|
|
|
|
group('Asserts in jumpToPage and animateToPage methods works properly', () {
|
|
Widget createPageView([PageController? controller]) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: PageView(
|
|
controller: controller,
|
|
children: <Widget>[
|
|
Container(color: Colors.red),
|
|
Container(color: Colors.green),
|
|
Container(color: Colors.blue),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
group('One pageController is attached to multiple PageViews', () {
|
|
Widget createMultiplePageViews(PageController controller) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Column(
|
|
children: <Widget>[
|
|
Expanded(
|
|
child: PageView(
|
|
controller: controller,
|
|
children: <Widget>[
|
|
Container(color: Colors.red),
|
|
Container(color: Colors.green),
|
|
Container(color: Colors.blue),
|
|
],
|
|
),
|
|
),
|
|
Expanded(
|
|
child: PageView(
|
|
controller: controller,
|
|
children: <Widget>[
|
|
Container(color: Colors.orange),
|
|
Container(color: Colors.purple),
|
|
Container(color: Colors.yellow),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
testWidgets(
|
|
'animateToPage assertion is working properly when pageController is attached to multiple PageViews',
|
|
(WidgetTester tester) async {
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(createMultiplePageViews(controller));
|
|
|
|
expect(
|
|
() => controller.animateToPage(
|
|
2,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.ease,
|
|
),
|
|
throwsA(
|
|
isAssertionError.having(
|
|
(AssertionError error) => error.message,
|
|
'message',
|
|
equals(
|
|
'Multiple PageViews are attached to '
|
|
'the same PageController.',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'jumpToPage assertion is working properly when pageController is attached to multiple PageViews',
|
|
(WidgetTester tester) async {
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(createMultiplePageViews(controller));
|
|
|
|
expect(
|
|
() => controller.jumpToPage(2),
|
|
throwsA(
|
|
isAssertionError.having(
|
|
(AssertionError error) => error.message,
|
|
'message',
|
|
equals(
|
|
'Multiple PageViews are attached to '
|
|
'the same PageController.',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
group('PageController is attached or is not attached to PageView', () {
|
|
testWidgets('Assert behavior of animateToPage works properly', (WidgetTester tester) async {
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
|
|
// pageController is not attached to PageView
|
|
await tester.pumpWidget(createPageView());
|
|
expect(
|
|
() => controller.animateToPage(
|
|
2,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.ease,
|
|
),
|
|
throwsA(
|
|
isAssertionError.having(
|
|
(AssertionError error) => error.message,
|
|
'message',
|
|
equals('PageController is not attached to a PageView.'),
|
|
),
|
|
),
|
|
);
|
|
|
|
// pageController is attached to PageView
|
|
await tester.pumpWidget(createPageView(controller));
|
|
expect(
|
|
() => controller.animateToPage(
|
|
2,
|
|
duration: const Duration(milliseconds: 300),
|
|
curve: Curves.ease,
|
|
),
|
|
returnsNormally,
|
|
);
|
|
});
|
|
|
|
testWidgets('Assert behavior of jumpToPage works properly', (WidgetTester tester) async {
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
|
|
// pageController is not attached to PageView
|
|
await tester.pumpWidget(createPageView());
|
|
expect(
|
|
() => controller.jumpToPage(2),
|
|
throwsA(
|
|
isAssertionError.having(
|
|
(AssertionError error) => error.message,
|
|
'message',
|
|
equals('PageController is not attached to a PageView.'),
|
|
),
|
|
),
|
|
);
|
|
|
|
// pageController is attached to PageView
|
|
await tester.pumpWidget(createPageView(controller));
|
|
expect(() => controller.jumpToPage(2), returnsNormally);
|
|
});
|
|
});
|
|
});
|
|
|
|
testWidgets(
|
|
'Get the page value before the content dimension is determined,do not throw an assertion and return null',
|
|
(WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/146986.
|
|
final controller = PageController();
|
|
late String currentPage;
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Scaffold(
|
|
body: PageView(
|
|
controller: controller,
|
|
children: <Widget>[
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
currentPage = controller.page == null ? 'null' : 'not empty';
|
|
return Center(child: Text(currentPage));
|
|
},
|
|
),
|
|
],
|
|
),
|
|
floatingActionButton: FloatingActionButton(
|
|
onPressed: () {
|
|
setState(() {});
|
|
},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(find.text('null'), findsOneWidget);
|
|
expect(find.text('not empty'), findsNothing);
|
|
expect(currentPage, 'null');
|
|
|
|
await tester.tap(find.byType(FloatingActionButton));
|
|
await tester.pump();
|
|
currentPage = controller.page == null ? 'null' : 'not empty';
|
|
expect(find.text('not empty'), findsOneWidget);
|
|
expect(find.text('null'), findsNothing);
|
|
expect(currentPage, 'not empty');
|
|
},
|
|
);
|
|
|
|
testWidgets('Does not crash when calling jumpToPage before layout', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/86222.
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Navigator(
|
|
onDidRemovePage: (Page<Object?> page) {},
|
|
pages: <Page<void>>[
|
|
MaterialPage<void>(
|
|
child: Scaffold(
|
|
body: PageView(
|
|
controller: controller,
|
|
children: const <Widget>[
|
|
Scaffold(body: Text('One')),
|
|
Scaffold(body: Text('Two')),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const MaterialPage<void>(child: Scaffold()),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
controller.jumpToPage(1);
|
|
expect(tester.takeException(), null);
|
|
});
|
|
|
|
testWidgets('Does not crash when calling animateToPage before layout', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/86222.
|
|
final controller = PageController();
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Navigator(
|
|
onDidRemovePage: (Page<Object?> page) {},
|
|
pages: <Page<void>>[
|
|
MaterialPage<void>(
|
|
child: Scaffold(
|
|
body: PageView(
|
|
controller: controller,
|
|
children: const <Widget>[
|
|
Scaffold(body: Text('One')),
|
|
Scaffold(body: Text('Two')),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
const MaterialPage<void>(child: Scaffold()),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
controller.animateToPage(1, duration: const Duration(milliseconds: 50), curve: Curves.bounceIn);
|
|
expect(tester.takeException(), null);
|
|
});
|
|
}
|