403 lines
14 KiB
Dart
403 lines
14 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_test/flutter_test.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
void main() {
|
|
testWidgets('SliverList reverse children (with keys)', (WidgetTester tester) async {
|
|
final List<int> items = List<int>.generate(20, (int i) => i);
|
|
const double itemHeight = 300.0;
|
|
const double viewportHeight = 500.0;
|
|
|
|
const double scrollPosition = 18 * itemHeight;
|
|
final ScrollController controller = ScrollController(initialScrollOffset: scrollPosition);
|
|
|
|
await tester.pumpWidget(_buildSliverList(
|
|
items: items,
|
|
controller: controller,
|
|
itemHeight: itemHeight,
|
|
viewportHeight: viewportHeight,
|
|
));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(controller.offset, scrollPosition);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 1'), findsNothing);
|
|
expect(find.text('Tile 18'), findsOneWidget);
|
|
expect(find.text('Tile 19'), findsOneWidget);
|
|
|
|
await tester.pumpWidget(_buildSliverList(
|
|
items: items.reversed.toList(),
|
|
controller: controller,
|
|
itemHeight: itemHeight,
|
|
viewportHeight: viewportHeight,
|
|
));
|
|
final int frames = await tester.pumpAndSettle();
|
|
expect(frames, 1); // ensures that there is no (animated) bouncing of the scrollable
|
|
|
|
expect(controller.offset, scrollPosition);
|
|
expect(find.text('Tile 19'), findsNothing);
|
|
expect(find.text('Tile 18'), findsNothing);
|
|
expect(find.text('Tile 1'), findsOneWidget);
|
|
expect(find.text('Tile 0'), findsOneWidget);
|
|
|
|
controller.jumpTo(0.0);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(controller.offset, 0.0);
|
|
expect(find.text('Tile 19'), findsOneWidget);
|
|
expect(find.text('Tile 18'), findsOneWidget);
|
|
expect(find.text('Tile 1'), findsNothing);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
});
|
|
|
|
testWidgets('SliverList replace children (with keys)', (WidgetTester tester) async {
|
|
final List<int> items = List<int>.generate(20, (int i) => i);
|
|
const double itemHeight = 300.0;
|
|
const double viewportHeight = 500.0;
|
|
|
|
const double scrollPosition = 18 * itemHeight;
|
|
final ScrollController controller = ScrollController(initialScrollOffset: scrollPosition);
|
|
|
|
await tester.pumpWidget(_buildSliverList(
|
|
items: items,
|
|
controller: controller,
|
|
itemHeight: itemHeight,
|
|
viewportHeight: viewportHeight,
|
|
));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(controller.offset, scrollPosition);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 1'), findsNothing);
|
|
expect(find.text('Tile 18'), findsOneWidget);
|
|
expect(find.text('Tile 19'), findsOneWidget);
|
|
|
|
await tester.pumpWidget(_buildSliverList(
|
|
items: items.map<int>((int i) => i + 100).toList(),
|
|
controller: controller,
|
|
itemHeight: itemHeight,
|
|
viewportHeight: viewportHeight,
|
|
));
|
|
final int frames = await tester.pumpAndSettle();
|
|
expect(frames, 1); // ensures that there is no (animated) bouncing of the scrollable
|
|
|
|
expect(controller.offset, scrollPosition);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 1'), findsNothing);
|
|
expect(find.text('Tile 18'), findsNothing);
|
|
expect(find.text('Tile 19'), findsNothing);
|
|
|
|
expect(find.text('Tile 100'), findsNothing);
|
|
expect(find.text('Tile 101'), findsNothing);
|
|
expect(find.text('Tile 118'), findsOneWidget);
|
|
expect(find.text('Tile 119'), findsOneWidget);
|
|
|
|
controller.jumpTo(0.0);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(controller.offset, 0.0);
|
|
expect(find.text('Tile 100'), findsOneWidget);
|
|
expect(find.text('Tile 101'), findsOneWidget);
|
|
expect(find.text('Tile 118'), findsNothing);
|
|
expect(find.text('Tile 119'), findsNothing);
|
|
});
|
|
|
|
testWidgets('SliverList replace with shorter children list (with keys)', (WidgetTester tester) async {
|
|
final List<int> items = List<int>.generate(20, (int i) => i);
|
|
const double itemHeight = 300.0;
|
|
const double viewportHeight = 500.0;
|
|
|
|
final double scrollPosition = items.length * itemHeight - viewportHeight;
|
|
final ScrollController controller = ScrollController(initialScrollOffset: scrollPosition);
|
|
|
|
await tester.pumpWidget(_buildSliverList(
|
|
items: items,
|
|
controller: controller,
|
|
itemHeight: itemHeight,
|
|
viewportHeight: viewportHeight,
|
|
));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(controller.offset, scrollPosition);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 1'), findsNothing);
|
|
expect(find.text('Tile 17'), findsNothing);
|
|
expect(find.text('Tile 18'), findsOneWidget);
|
|
expect(find.text('Tile 19'), findsOneWidget);
|
|
|
|
await tester.pumpWidget(_buildSliverList(
|
|
items: items.sublist(0, items.length - 1),
|
|
controller: controller,
|
|
itemHeight: itemHeight,
|
|
viewportHeight: viewportHeight,
|
|
));
|
|
final int frames = await tester.pumpAndSettle();
|
|
expect(frames, 1); // No animation when content shrinks suddenly.
|
|
|
|
expect(controller.offset, scrollPosition - itemHeight);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 1'), findsNothing);
|
|
expect(find.text('Tile 17'), findsOneWidget);
|
|
expect(find.text('Tile 18'), findsOneWidget);
|
|
expect(find.text('Tile 19'), findsNothing);
|
|
});
|
|
|
|
testWidgets('SliverList should layout first child in case of child reordering', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/35904.
|
|
List<String> items = <String>['1', '2'];
|
|
|
|
await tester.pumpWidget(_buildSliverListRenderWidgetChild(items));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Tile 1'), findsOneWidget);
|
|
expect(find.text('Tile 2'), findsOneWidget);
|
|
|
|
items = items.reversed.toList();
|
|
await tester.pumpWidget(_buildSliverListRenderWidgetChild(items));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Tile 1'), findsOneWidget);
|
|
expect(find.text('Tile 2'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('SliverList should recalculate inaccurate layout offset case 1', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/42142.
|
|
final List<int> items = List<int>.generate(20, (int i) => i);
|
|
final ScrollController controller = ScrollController();
|
|
await tester.pumpWidget(
|
|
_buildSliverList(
|
|
items: List<int>.from(items),
|
|
controller: controller,
|
|
itemHeight: 50,
|
|
viewportHeight: 200,
|
|
)
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.drag(find.text('Tile 2'), const Offset(0.0, -1000.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Viewport should be scrolled to the end of list.
|
|
expect(controller.offset, 800.0);
|
|
expect(find.text('Tile 15'), findsNothing);
|
|
expect(find.text('Tile 16'), findsOneWidget);
|
|
expect(find.text('Tile 17'), findsOneWidget);
|
|
expect(find.text('Tile 18'), findsOneWidget);
|
|
expect(find.text('Tile 19'), findsOneWidget);
|
|
|
|
// Prepends item to the list.
|
|
items.insert(0, -1);
|
|
await tester.pumpWidget(
|
|
_buildSliverList(
|
|
items: List<int>.from(items),
|
|
controller: controller,
|
|
itemHeight: 50,
|
|
viewportHeight: 200,
|
|
)
|
|
);
|
|
await tester.pump();
|
|
// We need second pump to ensure the scheduled animation gets run.
|
|
await tester.pumpAndSettle();
|
|
// Scroll offset should stay the same, and the items in viewport should be
|
|
// shifted by one.
|
|
expect(controller.offset, 800.0);
|
|
expect(find.text('Tile 14'), findsNothing);
|
|
expect(find.text('Tile 15'), findsOneWidget);
|
|
expect(find.text('Tile 16'), findsOneWidget);
|
|
expect(find.text('Tile 17'), findsOneWidget);
|
|
expect(find.text('Tile 18'), findsOneWidget);
|
|
expect(find.text('Tile 19'), findsNothing);
|
|
|
|
// Drags back to beginning and newly added item is visible.
|
|
await tester.drag(find.text('Tile 16'), const Offset(0.0, 1000.0));
|
|
await tester.pumpAndSettle();
|
|
expect(controller.offset, 0.0);
|
|
expect(find.text('Tile -1'), findsOneWidget);
|
|
expect(find.text('Tile 0'), findsOneWidget);
|
|
expect(find.text('Tile 1'), findsOneWidget);
|
|
expect(find.text('Tile 2'), findsOneWidget);
|
|
expect(find.text('Tile 3'), findsNothing);
|
|
|
|
});
|
|
|
|
testWidgets('SliverList should recalculate inaccurate layout offset case 2', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/42142.
|
|
final List<int> items = List<int>.generate(20, (int i) => i);
|
|
final ScrollController controller = ScrollController();
|
|
await tester.pumpWidget(
|
|
_buildSliverList(
|
|
items: List<int>.from(items),
|
|
controller: controller,
|
|
itemHeight: 50,
|
|
viewportHeight: 200,
|
|
)
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.drag(find.text('Tile 2'), const Offset(0.0, -1000.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Viewport should be scrolled to the end of list.
|
|
expect(controller.offset, 800.0);
|
|
expect(find.text('Tile 15'), findsNothing);
|
|
expect(find.text('Tile 16'), findsOneWidget);
|
|
expect(find.text('Tile 17'), findsOneWidget);
|
|
expect(find.text('Tile 18'), findsOneWidget);
|
|
expect(find.text('Tile 19'), findsOneWidget);
|
|
|
|
// Reorders item to the front. This should make item 19 to be first child
|
|
// with layout offset = null.
|
|
final int swap = items[19];
|
|
items[19] = items[3];
|
|
items[3] = swap;
|
|
|
|
await tester.pumpWidget(
|
|
_buildSliverList(
|
|
items: List<int>.from(items),
|
|
controller: controller,
|
|
itemHeight: 50,
|
|
viewportHeight: 200,
|
|
)
|
|
);
|
|
await tester.pump();
|
|
// We need second pump to ensure the scheduled animation gets run.
|
|
await tester.pumpAndSettle();
|
|
// Scroll offset should stay the same
|
|
expect(controller.offset, 800.0);
|
|
expect(find.text('Tile 14'), findsNothing);
|
|
expect(find.text('Tile 15'), findsNothing);
|
|
expect(find.text('Tile 16'), findsOneWidget);
|
|
expect(find.text('Tile 17'), findsOneWidget);
|
|
expect(find.text('Tile 18'), findsOneWidget);
|
|
expect(find.text('Tile 3'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('SliverList should start to perform layout from the initial child when there is no valid offset', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/66198.
|
|
bool isShow = true;
|
|
final ScrollController controller = ScrollController();
|
|
Widget buildSliverList(ScrollController controller) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox(
|
|
height: 200,
|
|
child: ListView(
|
|
controller: controller,
|
|
children: <Widget>[
|
|
if (isShow)
|
|
for (int i = 0; i < 20; i++)
|
|
SizedBox(
|
|
height: 50,
|
|
child: Text('Tile $i'),
|
|
),
|
|
const SizedBox(), // Use this widget to occupy the position where the offset is 0 when rebuild
|
|
const SizedBox(key: Key('key0'), height: 50.0),
|
|
const SizedBox(key: Key('key1'), height: 50.0),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildSliverList(controller));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Scrolling to the bottom.
|
|
await tester.drag(find.text('Tile 2'), const Offset(0.0, -1000.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Viewport should be scrolled to the end of list.
|
|
expect(controller.offset, 900.0);
|
|
expect(find.text('Tile 17'), findsNothing);
|
|
expect(find.text('Tile 18'), findsOneWidget);
|
|
expect(find.text('Tile 19'), findsOneWidget);
|
|
expect(find.byKey(const Key('key0')), findsOneWidget);
|
|
expect(find.byKey(const Key('key1')), findsOneWidget);
|
|
|
|
// Trigger rebuild.
|
|
isShow = false;
|
|
await tester.pumpWidget(buildSliverList(controller));
|
|
|
|
// After rebuild, [ContainerRenderObjectMixin] has two children, and
|
|
// neither of them has a valid layout offset.
|
|
// SliverList can layout normally without any assert or dead loop.
|
|
// Only the 'SizeBox' show in the viewport.
|
|
expect(controller.offset, 0.0);
|
|
expect(find.text('Tile 0'), findsNothing);
|
|
expect(find.text('Tile 19'), findsNothing);
|
|
expect(find.byKey(const Key('key0')), findsOneWidget);
|
|
expect(find.byKey(const Key('key1')), findsOneWidget);
|
|
});
|
|
}
|
|
|
|
Widget _buildSliverListRenderWidgetChild(List<String> items) {
|
|
return MaterialApp(
|
|
home: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Material(
|
|
child: SizedBox(
|
|
height: 500,
|
|
child: CustomScrollView(
|
|
controller: ScrollController(),
|
|
slivers: <Widget>[
|
|
SliverList(
|
|
delegate: SliverChildListDelegate(
|
|
items.map<Widget>((String item) {
|
|
return Chip(
|
|
key: Key(item),
|
|
label: Text('Tile $item'),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSliverList({
|
|
List<int> items = const <int>[],
|
|
ScrollController? controller,
|
|
double itemHeight = 500.0,
|
|
double viewportHeight = 300.0,
|
|
}) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: SizedBox(
|
|
height: viewportHeight,
|
|
child: CustomScrollView(
|
|
controller: controller,
|
|
slivers: <Widget>[
|
|
SliverList(
|
|
delegate: SliverChildBuilderDelegate(
|
|
(BuildContext context, int i) {
|
|
return SizedBox(
|
|
key: ValueKey<int>(items[i]),
|
|
height: itemHeight,
|
|
child: Text('Tile ${items[i]}'),
|
|
);
|
|
},
|
|
findChildIndexCallback: (Key key) {
|
|
final ValueKey<int> valueKey = key as ValueKey<int>;
|
|
final int index = items.indexOf(valueKey.value);
|
|
return index == -1 ? null : index;
|
|
},
|
|
childCount: items.length,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|