mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
WIP Commits separated as follows: - Update lints in analysis_options files - Run `dart fix --apply` - Clean up leftover analysis issues - Run `dart format .` in the right places. Local analysis and testing passes. Checking CI now. Part of https://github.com/flutter/flutter/issues/178827 - Adoption of flutter_lints in examples/api coming in a separate change (cc @loic-sharma) ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2217 lines
71 KiB
Dart
2217 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);
|
|
}
|