711 lines
25 KiB
Dart
711 lines
25 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/src/foundation/diagnostics.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
// Regression test for https://github.com/flutter/flutter/issues/100451
|
|
testWidgets('SliverAnimatedGrid.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 CustomScrollView(
|
|
slivers: <Widget>[
|
|
SliverAnimatedGrid(
|
|
initialItemCount: itemCount,
|
|
itemBuilder: (BuildContext context, int index, Animation<double> animation) =>
|
|
Container(key: Key('$index'), height: 2000.0),
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 100.0,
|
|
mainAxisSpacing: 10.0,
|
|
crossAxisSpacing: 10.0,
|
|
),
|
|
findChildIndexCallback: (Key key) {
|
|
finderCalled = true;
|
|
return null;
|
|
},
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
expect(finderCalled, false);
|
|
|
|
// Trigger update.
|
|
stateSetter(() => itemCount = 77);
|
|
await tester.pump();
|
|
|
|
expect(finderCalled, true);
|
|
});
|
|
|
|
testWidgets('AnimatedGrid', (WidgetTester tester) async {
|
|
Widget builder(BuildContext context, int index, Animation<double> animation) {
|
|
return SizedBox(height: 100.0, child: Center(child: Text('item $index')));
|
|
}
|
|
|
|
final listKey = GlobalKey<AnimatedGridState>();
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: AnimatedGrid(
|
|
key: listKey,
|
|
initialItemCount: 2,
|
|
itemBuilder: builder,
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 100.0,
|
|
mainAxisSpacing: 10.0,
|
|
crossAxisSpacing: 10.0,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
find.byWidgetPredicate((Widget widget) {
|
|
return widget is SliverAnimatedGrid &&
|
|
widget.initialItemCount == 2 &&
|
|
widget.itemBuilder == builder;
|
|
}),
|
|
findsOneWidget,
|
|
);
|
|
|
|
listKey.currentState!.insertItem(0);
|
|
await tester.pump();
|
|
expect(find.text('item 2'), findsOneWidget);
|
|
|
|
listKey.currentState!.removeItem(2, (BuildContext context, Animation<double> animation) {
|
|
return const SizedBox(height: 100.0, child: Center(child: Text('removing item')));
|
|
}, duration: const Duration(milliseconds: 100));
|
|
|
|
await tester.pump();
|
|
expect(find.text('removing item'), findsOneWidget);
|
|
expect(find.text('item 2'), findsNothing);
|
|
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('removing item'), findsNothing);
|
|
|
|
listKey.currentState!.insertAllItems(0, 2);
|
|
await tester.pump();
|
|
expect(find.text('item 2'), findsOneWidget);
|
|
expect(find.text('item 3'), findsOneWidget);
|
|
|
|
// Test for removeAllItems.
|
|
listKey.currentState!.removeAllItems((BuildContext context, Animation<double> animation) {
|
|
return const SizedBox(height: 100.0, child: Center(child: Text('removing item')));
|
|
}, duration: const Duration(milliseconds: 100));
|
|
|
|
await tester.pump();
|
|
expect(find.text('removing item'), findsWidgets);
|
|
expect(find.text('item 0'), findsNothing);
|
|
expect(find.text('item 1'), findsNothing);
|
|
expect(find.text('item 2'), findsNothing);
|
|
expect(find.text('item 3'), findsNothing);
|
|
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('removing item'), findsNothing);
|
|
});
|
|
|
|
group('SliverAnimatedGrid', () {
|
|
testWidgets('initialItemCount', (WidgetTester tester) async {
|
|
final animations = <int, Animation<double>>{};
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CustomScrollView(
|
|
slivers: <Widget>[
|
|
SliverAnimatedGrid(
|
|
initialItemCount: 2,
|
|
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
|
animations[index] = animation;
|
|
return SizedBox(height: 100.0, child: Center(child: Text('item $index')));
|
|
},
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 100.0,
|
|
mainAxisSpacing: 10.0,
|
|
crossAxisSpacing: 10.0,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('item 0'), findsOneWidget);
|
|
expect(find.text('item 1'), findsOneWidget);
|
|
expect(animations.containsKey(0), true);
|
|
expect(animations.containsKey(1), true);
|
|
expect(animations[0]!.value, 1.0);
|
|
expect(animations[1]!.value, 1.0);
|
|
});
|
|
|
|
testWidgets('insert', (WidgetTester tester) async {
|
|
final listKey = GlobalKey<SliverAnimatedGridState>();
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CustomScrollView(
|
|
slivers: <Widget>[
|
|
SliverAnimatedGrid(
|
|
key: listKey,
|
|
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
|
return ScaleTransition(
|
|
key: ValueKey<int>(index),
|
|
scale: animation,
|
|
child: SizedBox(height: 100.0, child: Center(child: Text('item $index'))),
|
|
);
|
|
},
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 100.0,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
double itemScale(int index) => tester
|
|
.widget<ScaleTransition>(find.byKey(ValueKey<int>(index), skipOffstage: false))
|
|
.scale
|
|
.value;
|
|
double itemLeft(int index) =>
|
|
tester.getTopLeft(find.byKey(ValueKey<int>(index), skipOffstage: false)).dx;
|
|
double itemRight(int index) =>
|
|
tester.getTopRight(find.byKey(ValueKey<int>(index), skipOffstage: false)).dx;
|
|
|
|
listKey.currentState!.insertItem(0, duration: const Duration(milliseconds: 100));
|
|
await tester.pump();
|
|
|
|
// Newly inserted item 0's scale should animate from 0 to 1
|
|
expect(itemScale(0), 0.0);
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(itemScale(0), 0.5);
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(itemScale(0), 1.0);
|
|
|
|
// The list now contains one fully expanded item at the top:
|
|
expect(find.text('item 0'), findsOneWidget);
|
|
expect(itemLeft(0), 0.0);
|
|
expect(itemRight(0), 100.0);
|
|
|
|
listKey.currentState!.insertItem(0, duration: const Duration(milliseconds: 100));
|
|
listKey.currentState!.insertItem(0, duration: const Duration(milliseconds: 100));
|
|
await tester.pump();
|
|
|
|
// The scale of the newly inserted items at index 0 and 1 should animate
|
|
// from 0 to 1.
|
|
// The scale of the original item, now at index 2, should remain 1.
|
|
expect(itemScale(0), 0.0);
|
|
expect(itemScale(1), 0.0);
|
|
expect(itemScale(2), 1.0);
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(itemScale(0), 0.5);
|
|
expect(itemScale(1), 0.5);
|
|
expect(itemScale(2), 1.0);
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(itemScale(0), 1.0);
|
|
expect(itemScale(1), 1.0);
|
|
expect(itemScale(2), 1.0);
|
|
|
|
// The newly inserted "item 1" and "item 2" appear above "item 0"
|
|
expect(find.text('item 0'), findsOneWidget);
|
|
expect(find.text('item 1'), findsOneWidget);
|
|
expect(find.text('item 2'), findsOneWidget);
|
|
expect(itemLeft(0), 0.0);
|
|
expect(itemRight(0), 100.0);
|
|
expect(itemLeft(1), 100.0);
|
|
expect(itemRight(1), 200.0);
|
|
expect(itemLeft(2), 200.0);
|
|
expect(itemRight(2), 300.0);
|
|
});
|
|
|
|
testWidgets('insertAll', (WidgetTester tester) async {
|
|
final listKey = GlobalKey<SliverAnimatedGridState>();
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CustomScrollView(
|
|
slivers: <Widget>[
|
|
SliverAnimatedGrid(
|
|
key: listKey,
|
|
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
|
return ScaleTransition(
|
|
key: ValueKey<int>(index),
|
|
scale: animation,
|
|
child: SizedBox(height: 100.0, child: Center(child: Text('item $index'))),
|
|
);
|
|
},
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 100.0,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
double itemScale(int index) => tester
|
|
.widget<ScaleTransition>(find.byKey(ValueKey<int>(index), skipOffstage: false))
|
|
.scale
|
|
.value;
|
|
double itemLeft(int index) =>
|
|
tester.getTopLeft(find.byKey(ValueKey<int>(index), skipOffstage: false)).dx;
|
|
double itemRight(int index) =>
|
|
tester.getTopRight(find.byKey(ValueKey<int>(index), skipOffstage: false)).dx;
|
|
|
|
listKey.currentState!.insertAllItems(0, 2, duration: const Duration(milliseconds: 100));
|
|
await tester.pump();
|
|
|
|
// Newly inserted items 0 & 1's scale should animate from 0 to 1
|
|
expect(itemScale(0), 0.0);
|
|
expect(itemScale(1), 0.0);
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(itemScale(0), 0.5);
|
|
expect(itemScale(1), 0.5);
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(itemScale(0), 1.0);
|
|
expect(itemScale(1), 1.0);
|
|
|
|
// The list now contains two fully expanded items at the top:
|
|
expect(find.text('item 0'), findsOneWidget);
|
|
expect(find.text('item 1'), findsOneWidget);
|
|
expect(itemLeft(0), 0.0);
|
|
expect(itemRight(0), 100.0);
|
|
expect(itemLeft(1), 100.0);
|
|
expect(itemRight(1), 200.0);
|
|
});
|
|
|
|
testWidgets('remove', (WidgetTester tester) async {
|
|
final listKey = GlobalKey<SliverAnimatedGridState>();
|
|
final items = <int>[0, 1, 2];
|
|
|
|
Widget buildItem(BuildContext context, int item, Animation<double> animation) {
|
|
return ScaleTransition(
|
|
key: ValueKey<int>(item),
|
|
scale: animation,
|
|
child: SizedBox(
|
|
height: 100.0,
|
|
child: Center(child: Text('item $item', textDirection: TextDirection.ltr)),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CustomScrollView(
|
|
slivers: <Widget>[
|
|
SliverAnimatedGrid(
|
|
key: listKey,
|
|
initialItemCount: 3,
|
|
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
|
return buildItem(context, items[index], animation);
|
|
},
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 100.0,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
double itemScale(int index) => tester
|
|
.widget<ScaleTransition>(find.byKey(ValueKey<int>(index), skipOffstage: false))
|
|
.scale
|
|
.value;
|
|
double itemLeft(int index) =>
|
|
tester.getTopLeft(find.byKey(ValueKey<int>(index), skipOffstage: false)).dx;
|
|
double itemRight(int index) =>
|
|
tester.getTopRight(find.byKey(ValueKey<int>(index), skipOffstage: false)).dx;
|
|
|
|
expect(find.text('item 0'), findsOneWidget);
|
|
expect(find.text('item 1'), findsOneWidget);
|
|
expect(find.text('item 2'), findsOneWidget);
|
|
|
|
items.removeAt(0);
|
|
listKey.currentState!.removeItem(
|
|
0,
|
|
(BuildContext context, Animation<double> animation) => buildItem(context, 0, animation),
|
|
duration: const Duration(milliseconds: 100),
|
|
);
|
|
|
|
// Items 0, 1, 2 at 0, 100, 200. All heights 100.
|
|
expect(itemLeft(0), 0.0);
|
|
expect(itemRight(0), 100.0);
|
|
expect(itemLeft(1), 100.0);
|
|
expect(itemRight(1), 200.0);
|
|
expect(itemLeft(2), 200.0);
|
|
expect(itemRight(2), 300.0);
|
|
|
|
// Newly removed item 0's height should animate from 100 to 0 over 100ms
|
|
|
|
// Items 0, 1, 2 at 0, 50, 150. Item 0's height is 50.
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(itemScale(0), 0.5);
|
|
expect(itemScale(1), 1.0);
|
|
expect(itemScale(2), 1.0);
|
|
|
|
// Items 1, 2 at 0, 100.
|
|
await tester.pumpAndSettle();
|
|
expect(itemLeft(1), 0.0);
|
|
expect(itemRight(1), 100.0);
|
|
expect(itemLeft(2), 100.0);
|
|
expect(itemRight(2), 200.0);
|
|
});
|
|
|
|
testWidgets('removeAll', (WidgetTester tester) async {
|
|
final listKey = GlobalKey<SliverAnimatedGridState>();
|
|
final items = <int>[0, 1, 2];
|
|
|
|
Widget buildItem(BuildContext context, int item, Animation<double> animation) {
|
|
return ScaleTransition(
|
|
key: ValueKey<int>(item),
|
|
scale: animation,
|
|
child: SizedBox(
|
|
height: 100.0,
|
|
child: Center(child: Text('item $item', textDirection: TextDirection.ltr)),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CustomScrollView(
|
|
slivers: <Widget>[
|
|
SliverAnimatedGrid(
|
|
key: listKey,
|
|
initialItemCount: 3,
|
|
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
|
return buildItem(context, items[index], animation);
|
|
},
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 100.0,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(find.text('item 0'), findsOneWidget);
|
|
expect(find.text('item 1'), findsOneWidget);
|
|
expect(find.text('item 2'), findsOneWidget);
|
|
|
|
items.clear();
|
|
listKey.currentState!.removeAllItems(
|
|
(BuildContext context, Animation<double> animation) => buildItem(context, 0, animation),
|
|
duration: const Duration(milliseconds: 100),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('item 0'), findsNothing);
|
|
expect(find.text('item 1'), findsNothing);
|
|
expect(find.text('item 2'), findsNothing);
|
|
});
|
|
|
|
testWidgets('works in combination with other slivers', (WidgetTester tester) async {
|
|
final listKey = GlobalKey<SliverAnimatedGridState>();
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CustomScrollView(
|
|
slivers: <Widget>[
|
|
SliverList.list(
|
|
children: const <Widget>[SizedBox(height: 100), SizedBox(height: 100)],
|
|
),
|
|
SliverAnimatedGrid(
|
|
key: listKey,
|
|
initialItemCount: 3,
|
|
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
|
return SizedBox(height: 100, child: Text('item $index'));
|
|
},
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 100.0,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getTopLeft(find.text('item 0')).dx, 0);
|
|
expect(tester.getTopLeft(find.text('item 1')).dx, 100);
|
|
|
|
listKey.currentState!.insertItem(3);
|
|
await tester.pumpAndSettle();
|
|
expect(tester.getTopLeft(find.text('item 3')).dx, 300);
|
|
|
|
listKey.currentState!.removeItem(0, (BuildContext context, Animation<double> animation) {
|
|
return ScaleTransition(
|
|
scale: animation,
|
|
key: const ObjectKey('removing'),
|
|
child: const SizedBox(height: 100, child: Text('removing')),
|
|
);
|
|
}, duration: const Duration(seconds: 1));
|
|
|
|
await tester.pump();
|
|
expect(find.text('item 3'), findsNothing);
|
|
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
expect(
|
|
tester
|
|
.widget<ScaleTransition>(find.byKey(const ObjectKey('removing'), skipOffstage: false))
|
|
.scale
|
|
.value,
|
|
0.5,
|
|
);
|
|
expect(tester.getTopLeft(find.text('item 0')).dx, 100);
|
|
|
|
await tester.pumpAndSettle();
|
|
expect(find.text('removing'), findsNothing);
|
|
expect(tester.getTopLeft(find.text('item 0')).dx, 0);
|
|
});
|
|
|
|
testWidgets(
|
|
'passes correctly derived index of findChildIndexCallback to the inner SliverChildBuilderDelegate',
|
|
(WidgetTester tester) async {
|
|
final items = <int>[0, 1, 2, 3];
|
|
final listKey = GlobalKey<SliverAnimatedGridState>();
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CustomScrollView(
|
|
slivers: <Widget>[
|
|
SliverAnimatedGrid(
|
|
key: listKey,
|
|
initialItemCount: items.length,
|
|
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
|
return _StatefulListItem(key: ValueKey<int>(items[index]), index: index);
|
|
},
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 100.0,
|
|
mainAxisSpacing: 10.0,
|
|
crossAxisSpacing: 10.0,
|
|
),
|
|
findChildIndexCallback: (Key key) {
|
|
final int index = items.indexOf((key as ValueKey<int>).value);
|
|
return index == -1 ? null : index;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
// get all list entries in order
|
|
final List<Text> listEntries = find
|
|
.byType(Text)
|
|
.evaluate()
|
|
.map((Element e) => e.widget as Text)
|
|
.toList();
|
|
|
|
// check that the list is rendered in the correct order
|
|
expect(listEntries[0].data, equals('item 0'));
|
|
expect(listEntries[1].data, equals('item 1'));
|
|
expect(listEntries[2].data, equals('item 2'));
|
|
expect(listEntries[3].data, equals('item 3'));
|
|
|
|
// delete one item
|
|
listKey.currentState?.removeItem(0, (BuildContext context, Animation<double> animation) {
|
|
return Container();
|
|
});
|
|
|
|
// delete from list
|
|
items.removeAt(0);
|
|
|
|
// reorder list
|
|
items.insert(0, items.removeLast());
|
|
|
|
// render with new list order
|
|
await tester.pumpAndSettle();
|
|
|
|
// get all list entries in order
|
|
final List<Text> reorderedListEntries = find
|
|
.byType(Text)
|
|
.evaluate()
|
|
.map((Element e) => e.widget as Text)
|
|
.toList();
|
|
|
|
// check that the stateful items of the list are rendered in the order provided by findChildIndexCallback
|
|
expect(reorderedListEntries[0].data, equals('item 3'));
|
|
expect(reorderedListEntries[1].data, equals('item 1'));
|
|
expect(reorderedListEntries[2].data, equals('item 2'));
|
|
},
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'AnimatedGrid.of() and maybeOf called with a context that does not contain AnimatedGrid',
|
|
(WidgetTester tester) async {
|
|
final GlobalKey key = GlobalKey();
|
|
await tester.pumpWidget(Container(key: key));
|
|
late FlutterError error;
|
|
expect(AnimatedGrid.maybeOf(key.currentContext!), isNull);
|
|
try {
|
|
AnimatedGrid.of(key.currentContext!);
|
|
} on FlutterError catch (e) {
|
|
error = e;
|
|
}
|
|
expect(error.diagnostics.length, 4);
|
|
expect(error.diagnostics[2].level, DiagnosticLevel.hint);
|
|
expect(
|
|
error.diagnostics[2].toStringDeep(),
|
|
equalsIgnoringHashCodes(
|
|
'This can happen when the context provided is from the same\n'
|
|
'StatefulWidget that built the AnimatedGrid. Please see the\n'
|
|
'AnimatedGrid documentation for examples of how to refer to an\n'
|
|
'AnimatedGridState object:\n'
|
|
' https://api.flutter.dev/flutter/widgets/AnimatedGridState-class.html\n',
|
|
),
|
|
);
|
|
expect(error.diagnostics[3], isA<DiagnosticsProperty<Element>>());
|
|
expect(
|
|
error.toStringDeep(),
|
|
equalsIgnoringHashCodes(
|
|
'FlutterError\n'
|
|
' AnimatedGrid.of() called with a context that does not contain an\n'
|
|
' AnimatedGrid.\n'
|
|
' No AnimatedGrid ancestor could be found starting from the context\n'
|
|
' that was passed to AnimatedGrid.of().\n'
|
|
' This can happen when the context provided is from the same\n'
|
|
' StatefulWidget that built the AnimatedGrid. Please see the\n'
|
|
' AnimatedGrid documentation for examples of how to refer to an\n'
|
|
' AnimatedGridState object:\n'
|
|
' https://api.flutter.dev/flutter/widgets/AnimatedGridState-class.html\n'
|
|
' The context used was:\n'
|
|
' Container-[GlobalKey#32cc6]\n',
|
|
),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('AnimatedGrid.clipBehavior is forwarded to its inner CustomScrollView', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const Clip clipBehavior = Clip.none;
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: AnimatedGrid(
|
|
initialItemCount: 2,
|
|
clipBehavior: clipBehavior,
|
|
itemBuilder: (BuildContext context, int index, Animation<double> _) {
|
|
return SizedBox(height: 100.0, child: Center(child: Text('item $index')));
|
|
},
|
|
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
|
|
maxCrossAxisExtent: 100.0,
|
|
mainAxisSpacing: 10.0,
|
|
crossAxisSpacing: 10.0,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.widget<CustomScrollView>(find.byType(CustomScrollView)).clipBehavior,
|
|
clipBehavior,
|
|
);
|
|
});
|
|
|
|
testWidgets('AnimatedGrid applies MediaQuery padding', (WidgetTester tester) async {
|
|
const padding = EdgeInsets.all(30.0);
|
|
EdgeInsets? innerMediaQueryPadding;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(padding: EdgeInsets.all(30.0)),
|
|
child: AnimatedGrid(
|
|
initialItemCount: 6,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
|
|
itemBuilder: (BuildContext context, int index, Animation<double> animation) {
|
|
innerMediaQueryPadding = MediaQuery.paddingOf(context);
|
|
return const Placeholder();
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final Offset topLeft = tester.getTopLeft(find.byType(Placeholder).first);
|
|
// Automatically apply the top padding into sliver.
|
|
expect(topLeft, Offset(0.0, padding.top));
|
|
|
|
// Scroll to the bottom.
|
|
await tester.drag(find.byType(AnimatedGrid), const Offset(0.0, -1000.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
final Offset bottomRight = tester.getBottomRight(find.byType(Placeholder).last);
|
|
// Automatically apply the bottom padding into sliver.
|
|
expect(bottomRight, Offset(800.0, 600.0 - padding.bottom));
|
|
|
|
// Verify that the left/right padding is not applied.
|
|
expect(innerMediaQueryPadding, const EdgeInsets.symmetric(horizontal: 30.0));
|
|
});
|
|
|
|
testWidgets('AnimatedGrid does not crash at zero area', (WidgetTester tester) async {
|
|
tester.view.physicalSize = Size.zero;
|
|
final controller = ScrollController();
|
|
addTearDown(tester.view.reset);
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Center(
|
|
child: AnimatedGrid(
|
|
controller: controller,
|
|
itemBuilder: (_, int index, _) => Text('$index'),
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(AnimatedGrid)), Size.zero);
|
|
await controller.animateTo(
|
|
0,
|
|
duration: const Duration(milliseconds: 250),
|
|
curve: Curves.easeIn,
|
|
);
|
|
await tester.pump();
|
|
});
|
|
}
|
|
|
|
class _StatefulListItem extends StatefulWidget {
|
|
const _StatefulListItem({super.key, required this.index});
|
|
|
|
final int index;
|
|
|
|
@override
|
|
_StatefulListItemState createState() => _StatefulListItemState();
|
|
}
|
|
|
|
class _StatefulListItemState extends State<_StatefulListItem> {
|
|
late final int number = widget.index;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Text('item $number');
|
|
}
|
|
}
|