2216 lines
71 KiB
Dart
2216 lines
71 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 'dart:ui';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
testWidgets('CarouselView defaults', (WidgetTester tester) async {
|
|
final theme = ThemeData();
|
|
final ColorScheme colorScheme = theme.colorScheme;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 200,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder carouselViewMaterial = find
|
|
.descendant(of: find.byType(CarouselView), matching: find.byType(Material))
|
|
.first;
|
|
|
|
final Material material = tester.widget<Material>(carouselViewMaterial);
|
|
expect(material.clipBehavior, Clip.antiAlias);
|
|
expect(material.color, colorScheme.surface);
|
|
expect(material.elevation, 0.0);
|
|
expect(
|
|
material.shape,
|
|
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(28.0))),
|
|
);
|
|
});
|
|
|
|
testWidgets('CarouselView items customization', (WidgetTester tester) async {
|
|
final Key key = UniqueKey();
|
|
final theme = ThemeData();
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
padding: const EdgeInsets.all(20.0),
|
|
backgroundColor: Colors.amber,
|
|
elevation: 10.0,
|
|
shape: const StadiumBorder(),
|
|
overlayColor: WidgetStateProperty.resolveWith((Set<WidgetState> states) {
|
|
if (states.contains(WidgetState.pressed)) {
|
|
return Colors.yellow;
|
|
}
|
|
if (states.contains(WidgetState.hovered)) {
|
|
return Colors.red;
|
|
}
|
|
if (states.contains(WidgetState.focused)) {
|
|
return Colors.purple;
|
|
}
|
|
return null;
|
|
}),
|
|
itemExtent: 200,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
if (index == 0) {
|
|
return Center(
|
|
key: key,
|
|
child: Center(child: Text('Item $index')),
|
|
);
|
|
}
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder carouselViewMaterial = find
|
|
.descendant(of: find.byType(CarouselView), matching: find.byType(Material))
|
|
.first;
|
|
|
|
expect(
|
|
tester.getSize(carouselViewMaterial).width,
|
|
200 - 20 - 20,
|
|
); // Padding is 20 on both side.
|
|
final Material material = tester.widget<Material>(carouselViewMaterial);
|
|
expect(material.color, Colors.amber);
|
|
expect(material.elevation, 10.0);
|
|
expect(material.shape, const StadiumBorder());
|
|
|
|
RenderObject inkFeatures = tester.allRenderObjects.firstWhere(
|
|
(RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures',
|
|
);
|
|
|
|
// On hovered.
|
|
final TestGesture gesture = await hoverPointerOverCarouselItem(tester, key);
|
|
await tester.pumpAndSettle();
|
|
expect(inkFeatures, paints..rect(color: Colors.red.withOpacity(1.0)));
|
|
|
|
// On pressed.
|
|
await tester.pumpAndSettle();
|
|
await gesture.down(tester.getCenter(find.byKey(key)));
|
|
await tester.pumpAndSettle();
|
|
inkFeatures = tester.allRenderObjects.firstWhere(
|
|
(RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures',
|
|
);
|
|
expect(
|
|
inkFeatures,
|
|
paints
|
|
..rect()
|
|
..rect(color: Colors.yellow.withOpacity(1.0)),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
await gesture.up();
|
|
await gesture.removePointer();
|
|
|
|
// On focused.
|
|
final Element inkWellElement = tester.element(
|
|
find.descendant(of: carouselViewMaterial, matching: find.byType(InkWell)),
|
|
);
|
|
expect(inkWellElement.widget, isA<InkWell>());
|
|
final inkWell = inkWellElement.widget as InkWell;
|
|
|
|
const WidgetState state = WidgetState.focused;
|
|
|
|
// Check overlay color in focused state.
|
|
expect(inkWell.overlayColor?.resolve(<WidgetState>{state}), Colors.purple);
|
|
});
|
|
|
|
testWidgets('CarouselView respects onTap', (WidgetTester tester) async {
|
|
final keys = List<Key>.generate(10, (_) => UniqueKey());
|
|
final theme = ThemeData();
|
|
var tapIndex = 0;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 50,
|
|
onTap: (int index) {
|
|
tapIndex = index;
|
|
},
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(key: keys[index], child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder item1 = find.byKey(keys[1]);
|
|
await tester.tap(find.ancestor(of: item1, matching: find.byType(Stack)));
|
|
await tester.pump();
|
|
expect(tapIndex, 1);
|
|
|
|
final Finder item2 = find.byKey(keys[2]);
|
|
await tester.tap(find.ancestor(of: item2, matching: find.byType(Stack)));
|
|
await tester.pump();
|
|
expect(tapIndex, 2);
|
|
});
|
|
|
|
testWidgets('CarouselView layout (Uncontained layout)', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 250,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Size viewportSize = MediaQuery.sizeOf(tester.element(find.byType(CarouselView)));
|
|
expect(viewportSize, const Size(800, 600));
|
|
|
|
expect(find.text('Item 0'), findsOneWidget);
|
|
final Rect rect0 = tester.getRect(getItem(0));
|
|
expect(rect0, const Rect.fromLTRB(0.0, 0.0, 250.0, 600.0));
|
|
|
|
expect(find.text('Item 1'), findsOneWidget);
|
|
final Rect rect1 = tester.getRect(getItem(1));
|
|
expect(rect1, const Rect.fromLTRB(250.0, 0.0, 500.0, 600.0));
|
|
|
|
expect(find.text('Item 2'), findsOneWidget);
|
|
final Rect rect2 = tester.getRect(getItem(2));
|
|
expect(rect2, const Rect.fromLTRB(500.0, 0.0, 750.0, 600.0));
|
|
|
|
expect(find.text('Item 3'), findsOneWidget);
|
|
final Rect rect3 = tester.getRect(getItem(3));
|
|
expect(rect3, const Rect.fromLTRB(750.0, 0.0, 800.0, 600.0));
|
|
|
|
expect(find.text('Item 4'), findsNothing);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted layout', (WidgetTester tester) async {
|
|
Widget buildCarouselView({required List<int> weights}) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
flexWeights: weights,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildCarouselView(weights: <int>[4, 3, 2, 1]));
|
|
|
|
final Size viewportSize = MediaQuery.of(tester.element(find.byType(CarouselView))).size;
|
|
expect(viewportSize, const Size(800, 600));
|
|
|
|
expect(find.text('Item 0'), findsOneWidget);
|
|
Rect rect0 = tester.getRect(getItem(0));
|
|
// Item width is 4/10 of the viewport.
|
|
expect(rect0, const Rect.fromLTRB(0.0, 0.0, 320.0, 600.0));
|
|
|
|
expect(find.text('Item 1'), findsOneWidget);
|
|
Rect rect1 = tester.getRect(getItem(1));
|
|
// Item width is 3/10 of the viewport.
|
|
expect(rect1, const Rect.fromLTRB(320.0, 0.0, 560.0, 600.0));
|
|
|
|
expect(find.text('Item 2'), findsOneWidget);
|
|
final Rect rect2 = tester.getRect(getItem(2));
|
|
// Item width is 2/10 of the viewport.
|
|
expect(rect2, const Rect.fromLTRB(560.0, 0.0, 720.0, 600.0));
|
|
|
|
expect(find.text('Item 3'), findsOneWidget);
|
|
final Rect rect3 = tester.getRect(getItem(3));
|
|
// Item width is 1/10 of the viewport.
|
|
expect(rect3, const Rect.fromLTRB(720.0, 0.0, 800.0, 600.0));
|
|
|
|
expect(find.text('Item 4'), findsNothing);
|
|
|
|
// Test shorter weight list.
|
|
await tester.pumpWidget(buildCarouselView(weights: <int>[7, 1]));
|
|
await tester.pumpAndSettle();
|
|
expect(viewportSize, const Size(800, 600));
|
|
|
|
expect(find.text('Item 0'), findsOneWidget);
|
|
rect0 = tester.getRect(getItem(0));
|
|
// Item width is 7/8 of the viewport.
|
|
expect(rect0, const Rect.fromLTRB(0.0, 0.0, 700.0, 600.0));
|
|
|
|
expect(find.text('Item 1'), findsOneWidget);
|
|
rect1 = tester.getRect(getItem(1));
|
|
// Item width is 1/8 of the viewport.
|
|
expect(rect1, const Rect.fromLTRB(700.0, 0.0, 800.0, 600.0));
|
|
|
|
expect(find.text('Item 2'), findsNothing);
|
|
});
|
|
|
|
testWidgets('CarouselController initialItem', (WidgetTester tester) async {
|
|
final controller = CarouselController(initialItem: 5);
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
controller: controller,
|
|
itemExtent: 400,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Size viewportSize = MediaQuery.sizeOf(tester.element(find.byType(CarouselView)));
|
|
expect(viewportSize, const Size(800, 600));
|
|
|
|
expect(find.text('Item 5'), findsOneWidget);
|
|
final Rect rect5 = tester.getRect(getItem(5));
|
|
// Item width is 400.
|
|
expect(rect5, const Rect.fromLTRB(0.0, 0.0, 400.0, 600.0));
|
|
|
|
expect(find.text('Item 6'), findsOneWidget);
|
|
final Rect rect6 = tester.getRect(getItem(6));
|
|
// Item width is 400.
|
|
expect(rect6, const Rect.fromLTRB(400.0, 0.0, 800.0, 600.0));
|
|
|
|
expect(find.text('Item 4'), findsNothing);
|
|
expect(find.text('Item 7'), findsNothing);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted respects CarouselController.initialItem', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final controller = CarouselController(initialItem: 5);
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
controller: controller,
|
|
flexWeights: const <int>[7, 1],
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Size viewportSize = MediaQuery.of(tester.element(find.byType(CarouselView))).size;
|
|
expect(viewportSize, const Size(800, 600));
|
|
|
|
expect(find.text('Item 5'), findsOneWidget);
|
|
final Rect rect5 = tester.getRect(getItem(5));
|
|
// Item width is 7/8 of the viewport.
|
|
expect(rect5, const Rect.fromLTRB(0.0, 0.0, 700.0, 600.0));
|
|
|
|
expect(find.text('Item 6'), findsOneWidget);
|
|
final Rect rect6 = tester.getRect(getItem(6));
|
|
// Item width is 1/8 of the viewport.
|
|
expect(rect6, const Rect.fromLTRB(700.0, 0.0, 800.0, 600.0));
|
|
|
|
expect(find.text('Item 4'), findsNothing);
|
|
expect(find.text('Item 7'), findsNothing);
|
|
});
|
|
|
|
testWidgets('The initialItem should be the first item with expanded size(max extent)', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final controller = CarouselController(initialItem: 5);
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
controller: controller,
|
|
flexWeights: const <int>[1, 8, 1],
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Size viewportSize = MediaQuery.of(tester.element(find.byType(CarouselView))).size;
|
|
expect(viewportSize, const Size(800, 600));
|
|
|
|
// Item 5 should have be the expanded item.
|
|
expect(find.text('Item 5'), findsOneWidget);
|
|
final Rect rect5 = tester.getRect(getItem(5));
|
|
// Item width is 8/10 of the viewport.
|
|
expect(rect5, const Rect.fromLTRB(80.0, 0.0, 720.0, 600.0));
|
|
|
|
expect(find.text('Item 6'), findsOneWidget);
|
|
final Rect rect6 = tester.getRect(getItem(6));
|
|
// Item width is 1/10 of the viewport.
|
|
expect(rect6, const Rect.fromLTRB(720.0, 0.0, 800.0, 600.0));
|
|
|
|
expect(find.text('Item 4'), findsOneWidget);
|
|
final Rect rect4 = tester.getRect(getItem(4));
|
|
// Item width is 1/10 of the viewport.
|
|
expect(rect4, const Rect.fromLTRB(0.0, 0.0, 80.0, 600.0));
|
|
|
|
expect(find.text('Item 7'), findsNothing);
|
|
});
|
|
|
|
testWidgets('CarouselView respects itemSnapping', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemSnapping: true,
|
|
itemExtent: 300,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
void checkOriginalExpectations() {
|
|
expect(getItem(0), findsOneWidget);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsNothing);
|
|
}
|
|
|
|
checkOriginalExpectations();
|
|
|
|
// Snap back to the original item.
|
|
await tester.drag(getItem(0), const Offset(-150, 0));
|
|
await tester.pumpAndSettle();
|
|
|
|
checkOriginalExpectations();
|
|
|
|
// Snap back to the original item.
|
|
await tester.drag(getItem(0), const Offset(100, 0));
|
|
await tester.pumpAndSettle();
|
|
|
|
checkOriginalExpectations();
|
|
|
|
// Snap to the next item.
|
|
await tester.drag(getItem(0), const Offset(-200, 0));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsNothing);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted respects itemSnapping', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
itemSnapping: true,
|
|
consumeMaxWeight: false,
|
|
flexWeights: const <int>[1, 7],
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
void checkOriginalExpectations() {
|
|
expect(getItem(0), findsOneWidget);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsNothing);
|
|
}
|
|
|
|
checkOriginalExpectations();
|
|
|
|
// Snap back to the original item.
|
|
await tester.drag(getItem(0), const Offset(-20, 0));
|
|
await tester.pumpAndSettle();
|
|
|
|
checkOriginalExpectations();
|
|
|
|
// Snap back to the original item.
|
|
await tester.drag(getItem(0), const Offset(50, 0));
|
|
await tester.pumpAndSettle();
|
|
|
|
checkOriginalExpectations();
|
|
|
|
// Snap to the next item.
|
|
await tester.drag(getItem(0), const Offset(-70, 0));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsNothing);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsNothing);
|
|
});
|
|
|
|
testWidgets('CarouselView respect itemSnapping when fling', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemSnapping: true,
|
|
itemExtent: 300,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Show item 0, 1, and 2.
|
|
expect(getItem(0), findsOneWidget);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsNothing);
|
|
|
|
// Snap to the next item. Show item 1, 2 and 3.
|
|
await tester.fling(getItem(0), const Offset(-100, 0), 800);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsNothing);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsOneWidget);
|
|
expect(getItem(4), findsNothing);
|
|
|
|
// Snap to the next item. Show item 2, 3 and 4.
|
|
await tester.fling(getItem(1), const Offset(-100, 0), 800);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsNothing);
|
|
expect(getItem(1), findsNothing);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsOneWidget);
|
|
expect(getItem(4), findsOneWidget);
|
|
expect(getItem(5), findsNothing);
|
|
|
|
// Fling back to the previous item. Show item 1, 2 and 3.
|
|
await tester.fling(getItem(2), const Offset(100, 0), 800);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsOneWidget);
|
|
expect(getItem(4), findsNothing);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted respect itemSnapping when fling', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
itemSnapping: true,
|
|
consumeMaxWeight: false,
|
|
flexWeights: const <int>[1, 8, 1],
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('$index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
Finder getItem(int index) => find.descendant(
|
|
of: find.byType(CarouselView),
|
|
matching: find.ancestor(of: find.text('$index'), matching: find.byType(Padding)),
|
|
);
|
|
|
|
// Show item 0, 1, and 2.
|
|
expect(getItem(0), findsOneWidget);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsNothing);
|
|
|
|
// Should snap to item 2 because of a long drag(-100). Show item 2, 3 and 4.
|
|
await tester.fling(getItem(0), const Offset(-100, 0), 800);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsNothing);
|
|
expect(getItem(1), findsNothing);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsOneWidget);
|
|
expect(getItem(4), findsOneWidget);
|
|
|
|
// Fling to the next item (item 3). Show item 3, 4 and 5.
|
|
await tester.fling(getItem(2), const Offset(-50, 0), 800);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(2), findsNothing);
|
|
expect(getItem(3), findsOneWidget);
|
|
expect(getItem(4), findsOneWidget);
|
|
expect(getItem(5), findsOneWidget);
|
|
|
|
// Fling back to the previous item. Show item 2, 3 and 4.
|
|
await tester.fling(getItem(3), const Offset(50, 0), 800);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsOneWidget);
|
|
expect(getItem(4), findsOneWidget);
|
|
expect(getItem(5), findsNothing);
|
|
});
|
|
|
|
testWidgets('CarouselView respects scrollingDirection: Axis.vertical', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 200,
|
|
padding: EdgeInsets.zero,
|
|
scrollDirection: Axis.vertical,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsOneWidget);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsNothing);
|
|
final Rect rect0 = tester.getRect(getItem(0));
|
|
// Item width is 200 of the viewport.
|
|
expect(rect0, const Rect.fromLTRB(0.0, 0.0, 800.0, 200.0));
|
|
|
|
// Simulate a scroll up
|
|
await tester.drag(
|
|
find.byType(CarouselView),
|
|
const Offset(0, -200),
|
|
kind: PointerDeviceKind.trackpad,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(getItem(0), findsNothing);
|
|
expect(getItem(3), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted respects scrollingDirection: Axis.vertical', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
flexWeights: const <int>[3, 2, 1],
|
|
padding: EdgeInsets.zero,
|
|
scrollDirection: Axis.vertical,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsOneWidget);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsNothing);
|
|
final Rect rect0 = tester.getRect(getItem(0));
|
|
// Item width is 3/6 of the viewport.
|
|
expect(rect0, const Rect.fromLTRB(0.0, 0.0, 800.0, 300.0));
|
|
|
|
// Simulate a scroll up
|
|
await tester.drag(
|
|
find.byType(CarouselView),
|
|
const Offset(0, -300),
|
|
kind: PointerDeviceKind.trackpad,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(getItem(0), findsNothing);
|
|
expect(getItem(3), findsOneWidget);
|
|
});
|
|
|
|
testWidgets(
|
|
'CarouselView.weighted respects scrollingDirection: Axis.vertical + itemSnapping: true',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
itemSnapping: true,
|
|
flexWeights: const <int>[3, 2, 1],
|
|
scrollDirection: Axis.vertical,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsOneWidget);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsNothing);
|
|
final Rect rect0 = tester.getRect(getItem(0));
|
|
// Item width is 3/6 of the viewport.
|
|
expect(rect0, const Rect.fromLTRB(0.0, 0.0, 800.0, 300.0));
|
|
|
|
// Simulate a scroll up but less than half of the leading item, the leading
|
|
// item should go back to the original position because itemSnapping is set
|
|
// to true.
|
|
await tester.drag(
|
|
find.byType(CarouselView),
|
|
const Offset(0, -149),
|
|
kind: PointerDeviceKind.trackpad,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(getItem(0), findsOneWidget);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsNothing);
|
|
|
|
// Simulate a scroll up more than half of the leading item, the leading
|
|
// item continue to scrolling and will disappear when animation ends because
|
|
// itemSnapping is set to true.
|
|
await tester.drag(
|
|
find.byType(CarouselView),
|
|
const Offset(0, -151),
|
|
kind: PointerDeviceKind.trackpad,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(getItem(0), findsNothing);
|
|
expect(getItem(3), findsOneWidget);
|
|
},
|
|
);
|
|
|
|
testWidgets('CarouselView respects reverse', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 200,
|
|
reverse: true,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsOneWidget);
|
|
final Rect rect0 = tester.getRect(getItem(0));
|
|
// Item 0 should be placed on the end of the screen.
|
|
expect(rect0, const Rect.fromLTRB(600.0, 0.0, 800.0, 600.0));
|
|
|
|
expect(getItem(1), findsOneWidget);
|
|
final Rect rect1 = tester.getRect(getItem(1));
|
|
// Item 1 should be placed before item 0.
|
|
expect(rect1, const Rect.fromLTRB(400.0, 0.0, 600.0, 600.0));
|
|
|
|
expect(getItem(2), findsOneWidget);
|
|
final Rect rect2 = tester.getRect(getItem(2));
|
|
// Item 2 should be placed before item 1.
|
|
expect(rect2, const Rect.fromLTRB(200.0, 0.0, 400.0, 600.0));
|
|
|
|
expect(getItem(3), findsOneWidget);
|
|
final Rect rect3 = tester.getRect(getItem(3));
|
|
// Item 3 should be placed before item 2.
|
|
expect(rect3, const Rect.fromLTRB(0.0, 0.0, 200.0, 600.0));
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted respects reverse', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
flexWeights: const <int>[4, 3, 2, 1],
|
|
reverse: true,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsOneWidget);
|
|
final Rect rect0 = tester.getRect(getItem(0));
|
|
// Item 0 should be placed on the end of the screen.
|
|
const int item0Width = 80 * 4;
|
|
expect(rect0, const Rect.fromLTRB(800.0 - item0Width, 0.0, 800.0, 600.0));
|
|
|
|
expect(getItem(1), findsOneWidget);
|
|
final Rect rect1 = tester.getRect(getItem(1));
|
|
// Item 1 should be placed before item 0.
|
|
const int item1Width = 80 * 3;
|
|
expect(
|
|
rect1,
|
|
const Rect.fromLTRB(800.0 - item0Width - item1Width, 0.0, 800.0 - item0Width, 600.0),
|
|
);
|
|
|
|
expect(getItem(2), findsOneWidget);
|
|
final Rect rect2 = tester.getRect(getItem(2));
|
|
// Item 2 should be placed before item 1.
|
|
const int item2Width = 80 * 2;
|
|
expect(
|
|
rect2,
|
|
const Rect.fromLTRB(
|
|
800.0 - item0Width - item1Width - item2Width,
|
|
0.0,
|
|
800.0 - item0Width - item1Width,
|
|
600.0,
|
|
),
|
|
);
|
|
|
|
expect(getItem(3), findsOneWidget);
|
|
final Rect rect3 = tester.getRect(getItem(3));
|
|
// Item 3 should be placed before item 2.
|
|
expect(
|
|
rect3,
|
|
const Rect.fromLTRB(0.0, 0.0, 800.0 - item0Width - item1Width - item2Width, 600.0),
|
|
);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted respects reverse + vertical scroll direction', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
reverse: true,
|
|
flexWeights: const <int>[4, 3, 2, 1],
|
|
scrollDirection: Axis.vertical,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsOneWidget);
|
|
final Rect rect0 = tester.getRect(getItem(0));
|
|
// Item 0 should be placed on the end of the screen.
|
|
const int item0Height = 60 * 4;
|
|
expect(rect0, const Rect.fromLTRB(0.0, 600.0 - item0Height, 800.0, 600.0));
|
|
|
|
expect(getItem(1), findsOneWidget);
|
|
final Rect rect1 = tester.getRect(getItem(1));
|
|
// Item 1 should be placed before item 0.
|
|
const int item1Height = 60 * 3;
|
|
expect(
|
|
rect1,
|
|
const Rect.fromLTRB(0.0, 600.0 - item0Height - item1Height, 800.0, 600.0 - item0Height),
|
|
);
|
|
|
|
expect(getItem(2), findsOneWidget);
|
|
final Rect rect2 = tester.getRect(getItem(2));
|
|
// Item 2 should be placed before item 1.
|
|
const int item2Height = 60 * 2;
|
|
expect(
|
|
rect2,
|
|
const Rect.fromLTRB(
|
|
0.0,
|
|
600.0 - item0Height - item1Height - item2Height,
|
|
800.0,
|
|
600.0 - item0Height - item1Height,
|
|
),
|
|
);
|
|
|
|
expect(getItem(3), findsOneWidget);
|
|
final Rect rect3 = tester.getRect(getItem(3));
|
|
// Item 3 should be placed before item 2.
|
|
expect(
|
|
rect3,
|
|
const Rect.fromLTRB(0.0, 0.0, 800.0, 600.0 - item0Height - item1Height - item2Height),
|
|
);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted respects reverse + vertical scroll direction + itemSnapping', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
reverse: true,
|
|
flexWeights: const <int>[4, 3, 2, 1],
|
|
scrollDirection: Axis.vertical,
|
|
itemSnapping: true,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getItem(0), findsOneWidget);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsOneWidget);
|
|
expect(getItem(4), findsNothing);
|
|
final Rect rect0 = tester.getRect(getItem(0));
|
|
// Item height is 4/10 of the viewport.
|
|
expect(rect0, const Rect.fromLTRB(0.0, 360.0, 800.0, 600.0));
|
|
|
|
// Simulate a scroll down but less than half of the leading item, the leading
|
|
// item should go back to the original position because itemSnapping is set
|
|
// to true.
|
|
await tester.drag(
|
|
find.byType(CarouselView),
|
|
const Offset(0, 240 / 2 - 1),
|
|
kind: PointerDeviceKind.trackpad,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(getItem(0), findsOneWidget);
|
|
expect(getItem(1), findsOneWidget);
|
|
expect(getItem(2), findsOneWidget);
|
|
expect(getItem(3), findsOneWidget);
|
|
expect(getItem(4), findsNothing);
|
|
|
|
// Simulate a scroll down more than half of the leading item, the leading
|
|
// item continue to scrolling and will disappear when animation ends because
|
|
// itemSnapping is set to true.
|
|
await tester.drag(
|
|
find.byType(CarouselView),
|
|
const Offset(0, 240 / 2 + 1),
|
|
kind: PointerDeviceKind.trackpad,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(getItem(0), findsNothing);
|
|
expect(getItem(4), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('CarouselView respects shrinkExtent', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 350,
|
|
shrinkExtent: 300,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final Rect rect0 = tester.getRect(getItem(0));
|
|
expect(rect0, const Rect.fromLTRB(0.0, 0.0, 350.0, 600.0));
|
|
|
|
final Rect rect1 = tester.getRect(getItem(1));
|
|
expect(rect1, const Rect.fromLTRB(350.0, 0.0, 700.0, 600.0));
|
|
|
|
final Rect rect2 = tester.getRect(getItem(2));
|
|
// The extent of item 2 is 300, and only 100 is on screen.
|
|
expect(rect2, const Rect.fromLTRB(700.0, 0.0, 1000.0, 600.0));
|
|
|
|
await tester.drag(
|
|
find.byType(CarouselView),
|
|
const Offset(-50, 0),
|
|
kind: PointerDeviceKind.trackpad,
|
|
);
|
|
await tester.pump();
|
|
// The item 0 should be pinned and has a size change from 350 to 50.
|
|
expect(tester.getRect(getItem(0)), const Rect.fromLTRB(0.0, 0.0, 300.0, 600.0));
|
|
// Keep dragging to left, extent of item 0 won't change (still 300) and part of item 0 will
|
|
// be off screen.
|
|
await tester.drag(
|
|
find.byType(CarouselView),
|
|
const Offset(-50, 0),
|
|
kind: PointerDeviceKind.trackpad,
|
|
);
|
|
await tester.pump();
|
|
expect(tester.getRect(getItem(0)), const Rect.fromLTRB(-50, 0.0, 250, 600));
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted respects consumeMaxWeight', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
flexWeights: const <int>[1, 2, 4, 2, 1],
|
|
itemSnapping: true,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// The initial item is item 0. To make sure the layout stays the same, the
|
|
// first item should be placed at the middle of the screen and there are some
|
|
// white space as if there are two more shinked items before the first item.
|
|
final Rect rect0 = tester.getRect(getItem(0));
|
|
expect(rect0, const Rect.fromLTRB(240.0, 0.0, 560.0, 600.0));
|
|
|
|
for (var i = 0; i < 7; i++) {
|
|
await tester.drag(find.byType(CarouselView), const Offset(-80.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
}
|
|
|
|
// After scrolling the carousel 7 times, the last item(item 9) should be on
|
|
// the end of the screen.
|
|
expect(getItem(9), findsOneWidget);
|
|
expect(tester.getRect(getItem(9)), const Rect.fromLTRB(720.0, 0.0, 800.0, 600.0));
|
|
|
|
// Keep snapping twice. Item 9 should be fully expanded to the max size.
|
|
for (var i = 0; i < 2; i++) {
|
|
await tester.drag(find.byType(CarouselView), const Offset(-80.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
}
|
|
expect(getItem(9), findsOneWidget);
|
|
expect(tester.getRect(getItem(9)), const Rect.fromLTRB(240.0, 0.0, 560.0, 600.0));
|
|
});
|
|
|
|
testWidgets('The initialItem stays when the flexWeights is updated', (WidgetTester tester) async {
|
|
final controller = CarouselController(initialItem: 3);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget buildCarousel(List<int> flexWeights) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
controller: controller,
|
|
flexWeights: flexWeights,
|
|
itemSnapping: true,
|
|
children: List<Widget>.generate(20, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildCarousel(<int>[1, 1, 6, 1, 1]));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Item 0'), findsNothing);
|
|
for (var i = 1; i <= 5; i++) {
|
|
expect(find.text('Item $i'), findsOneWidget);
|
|
}
|
|
Rect rect3 = tester.getRect(getItem(3));
|
|
expect(rect3.center.dx, 400.0);
|
|
expect(rect3.center.dy, 300.0);
|
|
|
|
expect(find.text('Item 6'), findsNothing);
|
|
|
|
await tester.pumpWidget(buildCarousel(<int>[7, 1]));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Item 2'), findsNothing);
|
|
expect(find.text('Item 3'), findsOneWidget);
|
|
expect(find.text('Item 4'), findsOneWidget);
|
|
expect(find.text('Item 5'), findsNothing);
|
|
|
|
rect3 = tester.getRect(getItem(3));
|
|
expect(rect3, const Rect.fromLTRB(0.0, 0.0, 700.0, 600.0));
|
|
final Rect rect4 = tester.getRect(getItem(4));
|
|
expect(rect4, const Rect.fromLTRB(700.0, 0.0, 800.0, 600.0));
|
|
});
|
|
|
|
testWidgets('The item that currently occupies max weight stays when the flexWeights is updated', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final controller = CarouselController(initialItem: 3);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget buildCarousel(List<int> flexWeights) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
controller: controller,
|
|
flexWeights: flexWeights,
|
|
itemSnapping: true,
|
|
children: List<Widget>.generate(20, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildCarousel(<int>[1, 1, 6, 1, 1]));
|
|
await tester.pumpAndSettle();
|
|
// Item 3 is centered.
|
|
final Rect rect3 = tester.getRect(getItem(3));
|
|
expect(rect3.center.dx, 400.0);
|
|
expect(rect3.center.dy, 300.0);
|
|
|
|
// Simulate scroll to right and show item 4 to be the centered max item.
|
|
await tester.drag(find.byType(CarouselView), const Offset(-80.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Item 1'), findsNothing);
|
|
for (var i = 2; i <= 6; i++) {
|
|
expect(find.text('Item $i'), findsOneWidget);
|
|
}
|
|
Rect rect4 = tester.getRect(getItem(4));
|
|
expect(rect4.center.dx, 400.0);
|
|
expect(rect4.center.dy, 300.0);
|
|
|
|
await tester.pumpWidget(buildCarousel(<int>[7, 1]));
|
|
await tester.pumpAndSettle();
|
|
|
|
rect4 = tester.getRect(getItem(4));
|
|
expect(rect4, const Rect.fromLTRB(0.0, 0.0, 700.0, 600.0));
|
|
final Rect rect5 = tester.getRect(getItem(5));
|
|
expect(rect5, const Rect.fromLTRB(700.0, 0.0, 800.0, 600.0));
|
|
});
|
|
|
|
testWidgets('The initialItem stays when the itemExtent is updated', (WidgetTester tester) async {
|
|
final controller = CarouselController(initialItem: 3);
|
|
addTearDown(controller.dispose);
|
|
|
|
Widget buildCarousel(double itemExtent) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
controller: controller,
|
|
itemExtent: itemExtent,
|
|
itemSnapping: true,
|
|
children: List<Widget>.generate(20, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildCarousel(234.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
Offset rect3BottomRight = tester.getRect(getItem(3)).bottomRight;
|
|
expect(rect3BottomRight.dx, 234.0);
|
|
expect(rect3BottomRight.dy, 600.0);
|
|
|
|
await tester.pumpWidget(buildCarousel(400.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
rect3BottomRight = tester.getRect(getItem(3)).bottomRight;
|
|
expect(rect3BottomRight.dx, 400.0);
|
|
expect(rect3BottomRight.dy, 600.0);
|
|
|
|
await tester.pumpWidget(buildCarousel(100.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
rect3BottomRight = tester.getRect(getItem(3)).bottomRight;
|
|
expect(rect3BottomRight.dx, 100.0);
|
|
expect(rect3BottomRight.dy, 600.0);
|
|
});
|
|
|
|
testWidgets(
|
|
'While scrolling, one extra item will show at the end of the screen during items transition',
|
|
(WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
flexWeights: const <int>[1, 2, 4, 2, 1],
|
|
consumeMaxWeight: false,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
for (var i = 0; i < 5; i++) {
|
|
expect(getItem(i), findsOneWidget);
|
|
}
|
|
|
|
// Drag the first item to the middle. So the progress for the first item size change
|
|
// is 50%, original width is 80.
|
|
await tester.drag(getItem(0), const Offset(-40.0, 0.0), kind: PointerDeviceKind.trackpad);
|
|
await tester.pump();
|
|
expect(tester.getRect(getItem(0)).width, 40.0);
|
|
|
|
// The size of item 1 is changing to the size of item 0, so the size of item 1
|
|
// now should be item1.originalExtent - 50% * (item1.extent - item0.extent).
|
|
// Item1 originally should be 2/(1+2+4+2+1) * 800 = 160.0.
|
|
expect(tester.getRect(getItem(1)).width, 160 - 0.5 * (160 - 80));
|
|
|
|
// The extent of item 2 should be: item2.originalExtent - 50% * (item2.extent - item1.extent).
|
|
// the extent of item 2 originally should be 4/(1+2+4+2+1) * 800 = 320.0.
|
|
expect(tester.getRect(getItem(2)).width, 320 - 0.5 * (320 - 160));
|
|
|
|
// The extent of item 3 should be: item3.originalExtent + 50% * (item2.extent - item3.extent).
|
|
// the extent of item 3 originally should be 2/(1+2+4+2+1) * 800 = 160.0.
|
|
expect(tester.getRect(getItem(3)).width, 160 + 0.5 * (320 - 160));
|
|
|
|
// The extent of item 4 should be: item4.originalExtent + 50% * (item3.extent - item4.extent).
|
|
// the extent of item 4 originally should be 1/(1+2+4+2+1) * 800 = 80.0.
|
|
expect(tester.getRect(getItem(4)).width, 80 + 0.5 * (160 - 80));
|
|
|
|
// The sum of the first 5 items during transition is less than the screen width.
|
|
double sum = 0;
|
|
for (var i = 0; i < 5; i++) {
|
|
sum += tester.getRect(getItem(i)).width;
|
|
}
|
|
expect(sum, lessThan(MediaQuery.of(tester.element(find.byType(CarouselView))).size.width));
|
|
final double difference =
|
|
MediaQuery.of(tester.element(find.byType(CarouselView))).size.width - sum;
|
|
|
|
// One more item should show on screen to fill the rest of the viewport.
|
|
expect(getItem(5), findsOneWidget);
|
|
expect(tester.getRect(getItem(5)).width, difference);
|
|
},
|
|
);
|
|
|
|
testWidgets('Updating CarouselView does not cause exception', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/152787
|
|
var isLight = true;
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return MaterialApp(
|
|
theme: Theme.of(
|
|
context,
|
|
).copyWith(brightness: isLight ? Brightness.light : Brightness.dark),
|
|
home: Scaffold(
|
|
appBar: AppBar(
|
|
actions: <Widget>[
|
|
Switch(
|
|
value: isLight,
|
|
onChanged: (bool value) {
|
|
setState(() {
|
|
isLight = value;
|
|
});
|
|
},
|
|
),
|
|
],
|
|
),
|
|
body: CarouselView(
|
|
itemExtent: 100,
|
|
children: List<Widget>.generate(10, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byType(Switch));
|
|
await tester.pumpAndSettle();
|
|
|
|
// No exception.
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('The shrinkExtent should keep the same when the item is tapped', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final children = List<Widget>.generate(20, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxHeight: 200),
|
|
child: CarouselView(
|
|
itemExtent: 330,
|
|
onTap: (int idx) => setState(() {}),
|
|
children: children,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.getRect(getItem(0)).width, 330.0);
|
|
|
|
final Finder item1 = find.text('Item 1');
|
|
await tester.tap(find.ancestor(of: item1, matching: find.byType(Stack)));
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.getRect(getItem(0)).width, 330.0);
|
|
expect(tester.getRect(getItem(1)).width, 330.0);
|
|
// This should be less than 330.0 because the item is shrunk; width is 800.0 - 330.0 - 330.0
|
|
expect(tester.getRect(getItem(2)).width, 140.0);
|
|
});
|
|
|
|
testWidgets('CarouselView onTap is clickable', (WidgetTester tester) async {
|
|
var tappedIndex = -1;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 350,
|
|
onTap: (int index) {
|
|
tappedIndex = index;
|
|
},
|
|
children: List<Widget>.generate(3, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
final Finder carouselItem = find.text('Item 1');
|
|
await tester.tap(carouselItem, warnIfMissed: false);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify that the onTap callback was called with the correct index.
|
|
expect(tappedIndex, 1);
|
|
|
|
// Tap another item.
|
|
final Finder anotherCarouselItem = find.text('Item 2');
|
|
await tester.tap(anotherCarouselItem, warnIfMissed: false);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify that the onTap callback was called with the new index.
|
|
expect(tappedIndex, 2);
|
|
});
|
|
|
|
testWidgets('CarouselView with enableSplash true - children are not directly interactive', (
|
|
WidgetTester tester,
|
|
) async {
|
|
var buttonPressed = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 350,
|
|
children: List<Widget>.generate(3, (int index) {
|
|
return Center(
|
|
child: ElevatedButton(
|
|
onPressed: () => buttonPressed = true,
|
|
child: Text('Button $index'),
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.text('Button 1'), warnIfMissed: false);
|
|
expect(buttonPressed, isFalse);
|
|
});
|
|
|
|
testWidgets('CarouselView with enableSplash false - children are directly interactive', (
|
|
WidgetTester tester,
|
|
) async {
|
|
var buttonPressed = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 350,
|
|
enableSplash: false,
|
|
children: List<Widget>.generate(3, (int index) {
|
|
return Center(
|
|
child: ElevatedButton(
|
|
onPressed: () => buttonPressed = true,
|
|
child: Text('Button $index'),
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.text('Button 1'));
|
|
expect(buttonPressed, isTrue);
|
|
});
|
|
|
|
testWidgets(
|
|
'CarouselView with enableSplash false - container is clickable without triggering children onTap',
|
|
(WidgetTester tester) async {
|
|
var tappedIndex = -1;
|
|
var buttonPressed = false;
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 350,
|
|
enableSplash: false,
|
|
onTap: (int index) {
|
|
tappedIndex = index;
|
|
},
|
|
children: List<Widget>.generate(3, (int index) {
|
|
return Column(
|
|
children: <Widget>[
|
|
Text('Item $index'),
|
|
ElevatedButton(
|
|
onPressed: () => buttonPressed = true,
|
|
child: Text('Button $index'),
|
|
),
|
|
],
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
final Finder carouselItem = find.text('Item 1');
|
|
await tester.tap(carouselItem, warnIfMissed: false);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tappedIndex, 1);
|
|
expect(buttonPressed, false);
|
|
|
|
final Finder anotherCarouselItem = find.text('Item 2');
|
|
await tester.tap(anotherCarouselItem, warnIfMissed: false);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tappedIndex, 2);
|
|
expect(buttonPressed, false);
|
|
|
|
await tester.tap(find.text('Button 1'), warnIfMissed: false);
|
|
expect(buttonPressed, isTrue);
|
|
},
|
|
);
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/160679
|
|
testWidgets('CarouselView does not crash if itemExtent is zero', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: SizedBox(
|
|
width: 100,
|
|
child: CarouselView(
|
|
itemExtent: 0,
|
|
children: <Widget>[Container(color: Colors.red, width: 100, height: 100)],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/166067.
|
|
testWidgets('CarouselView should not crash when using PageStorageKey', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: NestedScrollView(
|
|
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
|
return const <Widget>[SliverAppBar()];
|
|
},
|
|
body: CustomScrollView(
|
|
key: const PageStorageKey<String>('key1'),
|
|
slivers: <Widget>[
|
|
SliverToBoxAdapter(
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxHeight: 50),
|
|
child: CarouselView.weighted(
|
|
flexWeights: const <int>[1, 2],
|
|
consumeMaxWeight: false,
|
|
children: List<Widget>.generate(20, (int index) {
|
|
return ColoredBox(
|
|
color: Colors.primaries[index % Colors.primaries.length].withValues(
|
|
alpha: 0.8,
|
|
),
|
|
child: const SizedBox.expand(),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/160679.
|
|
testWidgets('Does not crash when parent size is zero', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Scaffold(
|
|
body: SizedBox(
|
|
width: 0,
|
|
child: CarouselView(itemExtent: 40.0, children: <Widget>[FlutterLogo()]),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('itemExtent can be set to double.infinity', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(itemExtent: double.infinity, children: <Widget>[FlutterLogo()]),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Item extent is clamped to screen size.
|
|
final Size logoSize = tester.getSize(find.byType(FlutterLogo));
|
|
const itemHorizontalPadding = 8.0; // Default padding.
|
|
expect(logoSize.width, 800.0 - itemHorizontalPadding);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/163436.
|
|
testWidgets('Does not crash when initial viewport dimension is zero and itemExtent is fixed', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(Size.zero);
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
|
|
const fixedItemExtent = 60.0;
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(itemExtent: fixedItemExtent, children: <Widget>[FlutterLogo()]),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/163436.
|
|
testWidgets('Does not crash when initial viewport dimension is zero and itemExtent is infinite', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(Size.zero);
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(itemExtent: double.infinity, children: <Widget>[FlutterLogo()]),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/163436.
|
|
testWidgets('itemExtent is applied when viewport dimension is updated', (
|
|
WidgetTester tester,
|
|
) async {
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
|
|
const itemExtent = 60.0;
|
|
var showScrollbars = false;
|
|
|
|
Future<void> updateSurfaceSizeAndPump(Size size) async {
|
|
await tester.binding.setSurfaceSize(size);
|
|
|
|
// At startup, a warm-up frame can be produced before the Flutter engine has reported the
|
|
// initial view metrics. As a result, the first frame can be produced with a size of zero.
|
|
// This leads to several instances of _CarouselPosition being created and
|
|
// _CarouselPosition.absorb to be called.
|
|
// To correctly simulate this behavior in the test environment, one solution is to
|
|
// update the ScrollConfiguration. For instance by changing the ScrollBehavior.scrollbars
|
|
// value on each build.
|
|
showScrollbars = !showScrollbars;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: ScrollConfiguration(
|
|
behavior: const ScrollBehavior().copyWith(scrollbars: showScrollbars),
|
|
child: const CarouselView(
|
|
itemExtent: itemExtent,
|
|
children: <Widget>[FlutterLogo()],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Simulate an initial zero viewport dimension.
|
|
await updateSurfaceSizeAndPump(Size.zero);
|
|
await updateSurfaceSizeAndPump(const Size(500, 400));
|
|
|
|
final Size logoSize = tester.getSize(find.byType(FlutterLogo));
|
|
const itemHorizontalPadding = 8.0; // Default padding.
|
|
expect(logoSize.width, itemExtent - itemHorizontalPadding);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/167621.
|
|
testWidgets('CarouselView.weighted does not crash when parent size is zero', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Scaffold(
|
|
body: SizedBox(
|
|
width: 0,
|
|
child: CarouselView.weighted(
|
|
flexWeights: <int>[1, 2],
|
|
children: <Widget>[FlutterLogo(), FlutterLogo()],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/167621.
|
|
testWidgets('CarouselView.weighted does not crash when initial viewport dimension is zero', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.binding.setSurfaceSize(Size.zero);
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
flexWeights: <int>[1, 2],
|
|
children: <Widget>[FlutterLogo(), FlutterLogo()],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/167621.
|
|
testWidgets('CarouselView.weigted weigths are applied when viewport dimension is updated', (
|
|
WidgetTester tester,
|
|
) async {
|
|
addTearDown(() => tester.binding.setSurfaceSize(null));
|
|
final controller = CarouselController(initialItem: 1);
|
|
addTearDown(controller.dispose);
|
|
|
|
const firstWeight = 2;
|
|
const secondWeight = 3;
|
|
var showScrollbars = false;
|
|
|
|
Future<void> updateSurfaceSizeAndPump(Size size) async {
|
|
await tester.binding.setSurfaceSize(size);
|
|
|
|
// At startup, a warm-up frame can be produced before the Flutter engine has reported the
|
|
// initial view metrics. As a result, the first frame can be produced with a size of zero.
|
|
// This leads to several instances of _CarouselPosition being created and
|
|
// _CarouselPosition.absorb to be called.
|
|
// To correctly simulate this behavior in the test environment, one solution is to
|
|
// update the ScrollConfiguration. For instance by changing the ScrollBehavior.scrollbars
|
|
// value on each build.
|
|
showScrollbars = !showScrollbars;
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: ScrollConfiguration(
|
|
behavior: const ScrollBehavior().copyWith(scrollbars: showScrollbars),
|
|
child: CarouselView.weighted(
|
|
controller: controller,
|
|
flexWeights: const <int>[firstWeight, secondWeight],
|
|
children: List<Widget>.generate(20, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Simulate an initial zero viewport dimension.
|
|
await updateSurfaceSizeAndPump(Size.zero);
|
|
const double surfaceWidth = 500;
|
|
await updateSurfaceSizeAndPump(const Size(surfaceWidth, 400));
|
|
|
|
const int totalWeight = firstWeight + secondWeight;
|
|
|
|
expect(find.text('Item 0'), findsOne);
|
|
expect(find.text('Item 1'), findsOne);
|
|
|
|
final double firstItemWidth = tester.getRect(getItem(0)).width;
|
|
expect(firstItemWidth, surfaceWidth * firstWeight / totalWeight);
|
|
final double secondItemWidth = tester.getRect(getItem(1)).width;
|
|
expect(secondItemWidth, surfaceWidth * secondWeight / totalWeight);
|
|
});
|
|
|
|
testWidgets('CarouselView.builder creates items lazily', (WidgetTester tester) async {
|
|
final builtItems = <int>[];
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.builder(
|
|
itemExtent: 300.0,
|
|
itemCount: 1000,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
builtItems.add(index);
|
|
return Container(
|
|
color: Colors.blue[index % 9 * 100],
|
|
child: Center(child: Text('Item $index')),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Only visible items should be built initially.
|
|
expect(builtItems.length, lessThan(10));
|
|
expect(builtItems, contains(0));
|
|
expect(builtItems, contains(1));
|
|
|
|
// Scroll to a far item.
|
|
await tester.drag(find.byType(CarouselView), const Offset(-2000.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Clear built items to see what's built after scrolling.
|
|
builtItems.clear();
|
|
|
|
// Force rebuild by scrolling a bit more.
|
|
await tester.drag(find.byType(CarouselView), const Offset(-300.0, 0.0));
|
|
await tester.pump();
|
|
|
|
// Should have built new items, not the initial ones.
|
|
expect(builtItems, isNotEmpty);
|
|
expect(builtItems.every((int index) => index > 3), isTrue);
|
|
});
|
|
|
|
group('CarouselController.animateToItem', () {
|
|
testWidgets('CarouselView.weighted horizontal, not reversed, flexWeights [7,1]', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
flexWeights: <int>[7, 1],
|
|
numberOfChildren: 20,
|
|
scrollDirection: Axis.horizontal,
|
|
reverse: false,
|
|
);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted horizontal, reversed, flexWeights [7,1]', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
flexWeights: <int>[7, 1],
|
|
numberOfChildren: 20,
|
|
scrollDirection: Axis.horizontal,
|
|
reverse: true,
|
|
);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted vertical, not reversed, flexWeights [7,1]', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
flexWeights: <int>[7, 1],
|
|
numberOfChildren: 20,
|
|
scrollDirection: Axis.vertical,
|
|
reverse: false,
|
|
);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted vertical, reversed, flexWeights [7,1]', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
flexWeights: <int>[7, 1],
|
|
numberOfChildren: 20,
|
|
scrollDirection: Axis.vertical,
|
|
reverse: true,
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'CarouselView.weighted horizontal, not reversed, flexWeights [1,7] and consumeMaxWeight false',
|
|
(WidgetTester tester) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
flexWeights: <int>[1, 7],
|
|
numberOfChildren: 20,
|
|
scrollDirection: Axis.horizontal,
|
|
reverse: false,
|
|
consumeMaxWeight: false,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('CarouselView.weighted horizontal, reversed, flexWeights [1,7]', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
flexWeights: <int>[1, 7],
|
|
numberOfChildren: 20,
|
|
scrollDirection: Axis.horizontal,
|
|
reverse: true,
|
|
);
|
|
});
|
|
|
|
testWidgets(
|
|
'CarouselView.weighted vertical, not reversed, flexWeights [1,7] and consumeMaxWeight false',
|
|
(WidgetTester tester) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
flexWeights: <int>[1, 7],
|
|
numberOfChildren: 20,
|
|
scrollDirection: Axis.vertical,
|
|
consumeMaxWeight: false,
|
|
reverse: false,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'CarouselView.weighted vertical, reversed, flexWeights [1,7] and consumeMaxWeight false',
|
|
(WidgetTester tester) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
flexWeights: <int>[1, 7],
|
|
numberOfChildren: 20,
|
|
scrollDirection: Axis.vertical,
|
|
consumeMaxWeight: false,
|
|
reverse: true,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'CarouselView.weighted vertical, reversed, flexWeights [1,7] and consumeMaxWeight',
|
|
(WidgetTester tester) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
flexWeights: <int>[1, 7],
|
|
numberOfChildren: 20,
|
|
scrollDirection: Axis.vertical,
|
|
reverse: true,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('CarouselView.weightedBuilder creates items lazily with flex weights', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final builtItems = <int>[];
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weightedBuilder(
|
|
flexWeights: const <int>[2, 3, 1],
|
|
itemCount: 1000,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
builtItems.add(index);
|
|
return Container(
|
|
color: Colors.blue[index % 9 * 100],
|
|
child: Center(child: Text('Item $index')),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Only visible items should be built initially.
|
|
expect(builtItems.length, lessThan(10));
|
|
expect(builtItems, contains(0));
|
|
expect(builtItems, contains(1));
|
|
|
|
// Scroll to a far item.
|
|
await tester.drag(find.byType(CarouselView), const Offset(-2000.0, 0.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Clear built items to see what's built after scrolling.
|
|
builtItems.clear();
|
|
|
|
// Force rebuild by scrolling a bit more.
|
|
await tester.drag(find.byType(CarouselView), const Offset(-300.0, 0.0));
|
|
await tester.pump();
|
|
|
|
// Should have built new items, not the initial ones.
|
|
expect(builtItems, isNotEmpty);
|
|
expect(builtItems.every((int index) => index > 3), isTrue);
|
|
});
|
|
|
|
testWidgets('CarouselView horizontal, not reversed', (WidgetTester tester) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
numberOfChildren: 20,
|
|
scrollDirection: Axis.horizontal,
|
|
reverse: false,
|
|
);
|
|
});
|
|
|
|
testWidgets('CarouselView horizontal, reversed', (WidgetTester tester) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
numberOfChildren: 10,
|
|
scrollDirection: Axis.horizontal,
|
|
reverse: true,
|
|
);
|
|
});
|
|
|
|
testWidgets('CarouselView vertical, not reversed', (WidgetTester tester) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
numberOfChildren: 10,
|
|
scrollDirection: Axis.vertical,
|
|
reverse: false,
|
|
);
|
|
});
|
|
|
|
testWidgets('CarouselView vertical, reversed', (WidgetTester tester) async {
|
|
await runCarouselTest(
|
|
tester: tester,
|
|
numberOfChildren: 10,
|
|
scrollDirection: Axis.vertical,
|
|
reverse: true,
|
|
);
|
|
});
|
|
|
|
testWidgets('CarouselView positions items correctly', (WidgetTester tester) async {
|
|
const numberOfChildren = 5;
|
|
final controller = CarouselController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
flexWeights: const <int>[2, 3, 1],
|
|
controller: controller,
|
|
itemSnapping: true,
|
|
children: List<Widget>.generate(numberOfChildren, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Get the RenderBox of the CarouselView to determine its position and boundaries.
|
|
final RenderBox carouselBox = tester.renderObject(find.byType(CarouselView));
|
|
final Offset carouselPos = carouselBox.localToGlobal(Offset.zero);
|
|
final double carouselLeft = carouselPos.dx;
|
|
final double carouselRight = carouselLeft + carouselBox.size.width;
|
|
|
|
for (var i = 0; i < numberOfChildren; i++) {
|
|
controller.animateToItem(i, curve: Curves.easeInOut);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Item $i'), findsOneWidget);
|
|
|
|
// Get the item's RenderBox and determine its position.
|
|
final RenderBox itemBox = tester.renderObject(find.text('Item $i'));
|
|
final Rect itemRect = itemBox.localToGlobal(Offset.zero) & itemBox.size;
|
|
|
|
// Validate that the item is positioned within the CarouselView boundaries.
|
|
expect(itemRect.left, greaterThanOrEqualTo(carouselLeft));
|
|
expect(itemRect.right, lessThanOrEqualTo(carouselRight));
|
|
}
|
|
});
|
|
});
|
|
|
|
group('CarouselView item clipBehavior', () {
|
|
testWidgets('CarouselView Item clipBehavior defaults to Clip.antiAlias', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 350,
|
|
children: List<Widget>.generate(3, (int index) {
|
|
return Text('Item $index');
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Material material = tester.firstWidget<Material>(
|
|
find.ancestor(of: find.text('Item 0'), matching: find.byType(Material)),
|
|
);
|
|
|
|
expect(material.clipBehavior, Clip.antiAlias);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted Item clipBehavior defaults to Clip.antiAlias', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
flexWeights: const <int>[1, 1, 1],
|
|
children: List<Widget>.generate(3, (int index) {
|
|
return Text('Item $index');
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Material material = tester.firstWidget<Material>(
|
|
find.ancestor(of: find.text('Item 0'), matching: find.byType(Material)),
|
|
);
|
|
|
|
expect(material.clipBehavior, Clip.antiAlias);
|
|
});
|
|
|
|
testWidgets('CarouselView Item clipBehavior respects theme', (WidgetTester tester) async {
|
|
final theme = ThemeData(
|
|
carouselViewTheme: const CarouselViewThemeData(itemClipBehavior: Clip.hardEdge),
|
|
);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 350,
|
|
children: List<Widget>.generate(3, (int index) {
|
|
return Text('Item $index');
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Material material = tester.firstWidget<Material>(
|
|
find.ancestor(of: find.text('Item 0'), matching: find.byType(Material)),
|
|
);
|
|
|
|
expect(material.clipBehavior, Clip.hardEdge);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted item clipBehavior respects theme', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final theme = ThemeData(
|
|
carouselViewTheme: const CarouselViewThemeData(itemClipBehavior: Clip.hardEdge),
|
|
);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
flexWeights: const <int>[1, 1, 1],
|
|
children: List<Widget>.generate(3, (int index) {
|
|
return Text('Item $index');
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Material material = tester.firstWidget<Material>(
|
|
find.ancestor(of: find.text('Item 0'), matching: find.byType(Material)),
|
|
);
|
|
|
|
expect(material.clipBehavior, Clip.hardEdge);
|
|
});
|
|
});
|
|
|
|
testWidgets('CarouselView item clipBehavior respects custom itemClipBehavior', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView(
|
|
itemExtent: 350,
|
|
itemClipBehavior: Clip.hardEdge,
|
|
children: List<Widget>.generate(3, (int index) {
|
|
return Text('Item $index');
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Material material = tester.firstWidget<Material>(
|
|
find.ancestor(of: find.text('Item 0'), matching: find.byType(Material)),
|
|
);
|
|
|
|
expect(material.clipBehavior, Clip.hardEdge);
|
|
});
|
|
|
|
testWidgets('CarouselView.weighted item clipBehavior respects custom itemClipBehavior', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: CarouselView.weighted(
|
|
flexWeights: const <int>[1, 1, 1],
|
|
itemClipBehavior: Clip.hardEdge,
|
|
children: List<Widget>.generate(3, (int index) {
|
|
return Text('Item $index');
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Material material = tester.firstWidget<Material>(
|
|
find.ancestor(of: find.text('Item 0'), matching: find.byType(Material)),
|
|
);
|
|
|
|
expect(material.clipBehavior, Clip.hardEdge);
|
|
});
|
|
}
|
|
|
|
Finder getItem(int index) {
|
|
return find.descendant(
|
|
of: find.byType(CarouselView),
|
|
matching: find.ancestor(of: find.text('Item $index'), matching: find.byType(Padding)),
|
|
);
|
|
}
|
|
|
|
Future<TestGesture> hoverPointerOverCarouselItem(WidgetTester tester, Key key) async {
|
|
final Offset center = tester.getCenter(find.byKey(key));
|
|
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
|
|
// On hovered.
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
return gesture;
|
|
}
|
|
|
|
Future<void> runCarouselTest({
|
|
required WidgetTester tester,
|
|
List<int> flexWeights = const <int>[],
|
|
bool consumeMaxWeight = true,
|
|
required int numberOfChildren,
|
|
required Axis scrollDirection,
|
|
required bool reverse,
|
|
}) async {
|
|
final controller = CarouselController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: flexWeights.isEmpty
|
|
? CarouselView(
|
|
scrollDirection: scrollDirection,
|
|
reverse: reverse,
|
|
controller: controller,
|
|
itemSnapping: true,
|
|
itemExtent: 300,
|
|
children: List<Widget>.generate(numberOfChildren, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
)
|
|
: CarouselView.weighted(
|
|
flexWeights: flexWeights,
|
|
scrollDirection: scrollDirection,
|
|
reverse: reverse,
|
|
controller: controller,
|
|
itemSnapping: true,
|
|
consumeMaxWeight: consumeMaxWeight,
|
|
children: List<Widget>.generate(numberOfChildren, (int index) {
|
|
return Center(child: Text('Item $index'));
|
|
}),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
double realOffset() {
|
|
return tester.state<ScrollableState>(find.byType(Scrollable)).position.pixels;
|
|
}
|
|
|
|
// Calculate the index of the middle item.
|
|
// The calculation depends on the scroll direction (normal or reverse).
|
|
// For reverse scrolling, the middle item is calculated taking into account the end of the list,
|
|
// reversing the calculation so that the item that appears in the middle when scrolling is the correct one.
|
|
// For normal scrolling, we simply get the middle item.
|
|
final int middleIndex = reverse
|
|
? (numberOfChildren - 1 - (numberOfChildren / 2).round())
|
|
: (numberOfChildren / 2).round();
|
|
|
|
controller.animateToItem(
|
|
middleIndex,
|
|
duration: const Duration(milliseconds: 100),
|
|
curve: Curves.easeInOut,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify that the middle item is visible.
|
|
expect(find.text('Item $middleIndex'), findsOneWidget);
|
|
expect(realOffset(), controller.offset);
|
|
|
|
// Scroll to the first item.
|
|
controller.animateToItem(0, duration: const Duration(milliseconds: 100), curve: Curves.easeInOut);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify that the first item is visible.
|
|
expect(find.text('Item 0'), findsOneWidget);
|
|
expect(realOffset(), controller.offset);
|
|
}
|