mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
570 lines
17 KiB
Dart
570 lines
17 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.
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
testWidgets('CustomScrollView restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: CustomScrollView(
|
|
restorationId: 'list',
|
|
cacheExtent: 0,
|
|
slivers: <Widget>[
|
|
SliverList(
|
|
delegate: SliverChildListDelegate(
|
|
List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester);
|
|
});
|
|
|
|
testWidgets('ListView restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: ListView(
|
|
restorationId: 'list',
|
|
cacheExtent: 0,
|
|
children: List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester);
|
|
});
|
|
|
|
testWidgets('ListView.builder restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: ListView.builder(
|
|
restorationId: 'list',
|
|
cacheExtent: 0,
|
|
itemBuilder: (BuildContext context, int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester);
|
|
});
|
|
|
|
testWidgets('ListView.separated restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: ListView.separated(
|
|
restorationId: 'list',
|
|
cacheExtent: 0,
|
|
itemCount: 50,
|
|
separatorBuilder: (BuildContext context, int index) => const SizedBox.shrink(),
|
|
itemBuilder: (BuildContext context, int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester);
|
|
});
|
|
|
|
testWidgets('ListView.custom restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: ListView.custom(
|
|
restorationId: 'list',
|
|
cacheExtent: 0,
|
|
childrenDelegate: SliverChildListDelegate(
|
|
List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester);
|
|
});
|
|
|
|
testWidgets('GridView restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: GridView(
|
|
restorationId: 'grid',
|
|
cacheExtent: 0,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 1),
|
|
children: List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester);
|
|
});
|
|
|
|
testWidgets('GridView.builder restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: GridView.builder(
|
|
restorationId: 'grid',
|
|
cacheExtent: 0,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 1),
|
|
itemBuilder: (BuildContext context, int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester);
|
|
});
|
|
|
|
testWidgets('GridView.custom restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: GridView.custom(
|
|
restorationId: 'grid',
|
|
cacheExtent: 0,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 1),
|
|
childrenDelegate: SliverChildListDelegate(
|
|
List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester);
|
|
});
|
|
|
|
testWidgets('GridView.count restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: GridView.count(
|
|
restorationId: 'grid',
|
|
cacheExtent: 0,
|
|
crossAxisCount: 1,
|
|
children: List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester);
|
|
});
|
|
|
|
testWidgets('GridView.extent restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: GridView.extent(
|
|
restorationId: 'grid',
|
|
cacheExtent: 0,
|
|
maxCrossAxisExtent: 50,
|
|
children: List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester);
|
|
});
|
|
|
|
testWidgets('SingleChildScrollView restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: SingleChildScrollView(
|
|
restorationId: 'single',
|
|
child: Column(
|
|
children: List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getTopLeft(find.text('Tile 0')), Offset.zero);
|
|
expect(tester.getTopLeft(find.text('Tile 1')), const Offset(0, 50));
|
|
|
|
tester.state<ScrollableState>(find.byType(Scrollable)).position.jumpTo(525);
|
|
await tester.pump();
|
|
|
|
expect(tester.getTopLeft(find.text('Tile 0')), const Offset(0, -525));
|
|
expect(tester.getTopLeft(find.text('Tile 1')), const Offset(0, -475));
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(tester.state<ScrollableState>(find.byType(Scrollable)).position.pixels, 525);
|
|
expect(tester.getTopLeft(find.text('Tile 0')), const Offset(0, -525));
|
|
expect(tester.getTopLeft(find.text('Tile 1')), const Offset(0, -475));
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
tester.state<ScrollableState>(find.byType(Scrollable)).position.jumpTo(0);
|
|
await tester.pump();
|
|
|
|
expect(tester.getTopLeft(find.text('Tile 0')), Offset.zero);
|
|
expect(tester.getTopLeft(find.text('Tile 1')), const Offset(0, 50));
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(tester.state<ScrollableState>(find.byType(Scrollable)).position.pixels, 525);
|
|
expect(tester.getTopLeft(find.text('Tile 0')), const Offset(0, -525));
|
|
expect(tester.getTopLeft(find.text('Tile 1')), const Offset(0, -475));
|
|
});
|
|
|
|
testWidgets('PageView restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: PageView(
|
|
restorationId: 'pager',
|
|
children: List<Widget>.generate(
|
|
50,
|
|
(int index) => Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await pageViewScrollAndRestore(tester);
|
|
});
|
|
|
|
testWidgets('PageView.builder restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: PageView.builder(
|
|
restorationId: 'pager',
|
|
itemBuilder: (BuildContext context, int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await pageViewScrollAndRestore(tester);
|
|
});
|
|
|
|
testWidgets('PageView.custom restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: PageView.custom(
|
|
restorationId: 'pager',
|
|
childrenDelegate: SliverChildListDelegate(
|
|
List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await pageViewScrollAndRestore(tester);
|
|
});
|
|
|
|
testWidgets('ListWheelScrollView restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: ListWheelScrollView(
|
|
restorationId: 'wheel',
|
|
itemExtent: 50,
|
|
children: List<Widget>.generate(
|
|
50,
|
|
(int index) => Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester, secondOffset: 542);
|
|
});
|
|
|
|
testWidgets('ListWheelScrollView.useDelegate restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: ListWheelScrollView.useDelegate(
|
|
restorationId: 'wheel',
|
|
itemExtent: 50,
|
|
childDelegate: ListWheelChildListDelegate(
|
|
children: List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await restoreScrollAndVerify(tester, secondOffset: 542);
|
|
});
|
|
|
|
testWidgets('NestedScrollView restoration', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: TestHarness(
|
|
height: 200,
|
|
child: NestedScrollView(
|
|
restorationId: 'outer',
|
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
|
return <Widget>[
|
|
SliverOverlapAbsorber(
|
|
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
|
sliver: SliverAppBar(
|
|
title: const Text('Books'),
|
|
pinned: true,
|
|
expandedHeight: 150.0,
|
|
forceElevated: innerBoxIsScrolled,
|
|
),
|
|
),
|
|
];
|
|
},
|
|
body: ListView(
|
|
restorationId: 'inner',
|
|
cacheExtent: 0,
|
|
children: List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.renderObject<RenderSliver>(find.byType(SliverAppBar)).geometry!.paintExtent, 150);
|
|
expect(find.text('Tile 0'), findsOneWidget);
|
|
expect(find.text('Tile 10'), findsNothing);
|
|
|
|
await tester.drag(find.byType(NestedScrollView), const Offset(0, -500));
|
|
await tester.pump();
|
|
|
|
expect(tester.renderObject<RenderSliver>(find.byType(SliverAppBar)).geometry!.paintExtent, 56);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 10'), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(tester.renderObject<RenderSliver>(find.byType(SliverAppBar)).geometry!.paintExtent, 56);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 10'), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
await tester.drag(find.byType(NestedScrollView), const Offset(0, 600));
|
|
await tester.pump();
|
|
|
|
expect(tester.renderObject<RenderSliver>(find.byType(SliverAppBar)).geometry!.paintExtent, 150);
|
|
expect(find.text('Tile 0'), findsOneWidget);
|
|
expect(find.text('Tile 10'), findsNothing);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(tester.renderObject<RenderSliver>(find.byType(SliverAppBar)).geometry!.paintExtent, 56);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 10'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('RestorationData is flushed even if no frame is scheduled', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
TestHarness(
|
|
child: ListView(
|
|
restorationId: 'list',
|
|
cacheExtent: 0,
|
|
children: List<Widget>.generate(
|
|
50,
|
|
(int index) => SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $index'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Tile 0'), findsOneWidget);
|
|
expect(find.text('Tile 1'), findsOneWidget);
|
|
expect(find.text('Tile 10'), findsNothing);
|
|
expect(find.text('Tile 11'), findsNothing);
|
|
expect(find.text('Tile 12'), findsNothing);
|
|
|
|
final TestRestorationData initialData = await tester.getRestorationData();
|
|
final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byType(ListView)));
|
|
await gesture.moveBy(const Offset(0, -525));
|
|
await tester.pump();
|
|
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 1'), findsNothing);
|
|
expect(find.text('Tile 10'), findsOneWidget);
|
|
expect(find.text('Tile 11'), findsOneWidget);
|
|
expect(find.text('Tile 12'), findsOneWidget);
|
|
|
|
// Restoration data hasn't changed.
|
|
expect(await tester.getRestorationData(), initialData);
|
|
|
|
// Restoration data changes with up event.
|
|
await gesture.up();
|
|
await tester.pump();
|
|
expect(await tester.getRestorationData(), isNot(initialData));
|
|
});
|
|
}
|
|
|
|
Future<void> pageViewScrollAndRestore(WidgetTester tester) async {
|
|
expect(find.text('Tile 0'), findsOneWidget);
|
|
expect(find.text('Tile 10'), findsNothing);
|
|
|
|
tester.state<ScrollableState>(find.byType(Scrollable)).position.jumpTo(50.0 * 10);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 10'), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(tester.state<ScrollableState>(find.byType(Scrollable)).position.pixels, 50.0 * 10);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 10'), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
tester.state<ScrollableState>(find.byType(Scrollable)).position.jumpTo(0);
|
|
await tester.pump();
|
|
|
|
expect(find.text('Tile 0'), findsOneWidget);
|
|
expect(find.text('Tile 10'), findsNothing);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(tester.state<ScrollableState>(find.byType(Scrollable)).position.pixels, 50.0 * 10);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 10'), findsOneWidget);
|
|
}
|
|
|
|
Future<void> restoreScrollAndVerify(WidgetTester tester, {double secondOffset = 525}) async {
|
|
final Finder findScrollable = find.byElementPredicate((Element e) => e.widget is Scrollable);
|
|
|
|
expect(find.text('Tile 0'), findsOneWidget);
|
|
expect(find.text('Tile 1'), findsOneWidget);
|
|
expect(find.text('Tile 10'), findsNothing);
|
|
expect(find.text('Tile 11'), findsNothing);
|
|
expect(find.text('Tile 12'), findsNothing);
|
|
|
|
tester.state<ScrollableState>(findScrollable).position.jumpTo(secondOffset);
|
|
await tester.pump();
|
|
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 1'), findsNothing);
|
|
expect(find.text('Tile 10'), findsOneWidget);
|
|
expect(find.text('Tile 11'), findsOneWidget);
|
|
expect(find.text('Tile 12'), findsOneWidget);
|
|
|
|
await tester.restartAndRestore();
|
|
|
|
expect(tester.state<ScrollableState>(findScrollable).position.pixels, secondOffset);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 1'), findsNothing);
|
|
expect(find.text('Tile 10'), findsOneWidget);
|
|
expect(find.text('Tile 11'), findsOneWidget);
|
|
expect(find.text('Tile 12'), findsOneWidget);
|
|
|
|
final TestRestorationData data = await tester.getRestorationData();
|
|
tester.state<ScrollableState>(findScrollable).position.jumpTo(0);
|
|
await tester.pump();
|
|
|
|
expect(find.text('Tile 0'), findsOneWidget);
|
|
expect(find.text('Tile 1'), findsOneWidget);
|
|
expect(find.text('Tile 10'), findsNothing);
|
|
expect(find.text('Tile 11'), findsNothing);
|
|
expect(find.text('Tile 12'), findsNothing);
|
|
|
|
await tester.restoreFrom(data);
|
|
|
|
expect(tester.state<ScrollableState>(findScrollable).position.pixels, secondOffset);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 1'), findsNothing);
|
|
expect(find.text('Tile 10'), findsOneWidget);
|
|
expect(find.text('Tile 11'), findsOneWidget);
|
|
expect(find.text('Tile 12'), findsOneWidget);
|
|
}
|
|
|
|
class TestHarness extends StatelessWidget {
|
|
const TestHarness({super.key, required this.child, this.height = 100});
|
|
|
|
final Widget child;
|
|
final double height;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return RootRestorationScope(
|
|
restorationId: 'root',
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Align(
|
|
alignment: Alignment.topLeft,
|
|
child: SizedBox(
|
|
height: height,
|
|
width: 50,
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|