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
1915 lines
61 KiB
Dart
1915 lines
61 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:math';
|
|
|
|
import 'package:flutter/gestures.dart' show DragStartBehavior;
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart' show LogicalKeyboardKey;
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'states.dart';
|
|
|
|
class ItemWidget extends StatefulWidget {
|
|
const ItemWidget({super.key, required this.value});
|
|
final String value;
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _ItemWidgetState();
|
|
}
|
|
|
|
class _ItemWidgetState extends State<ItemWidget> {
|
|
int randomInt = Random().nextInt(1000);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Text('${widget.value}: $randomInt');
|
|
}
|
|
}
|
|
|
|
class MaterialLocalizationsDelegate extends LocalizationsDelegate<MaterialLocalizations> {
|
|
@override
|
|
bool isSupported(Locale locale) => true;
|
|
|
|
@override
|
|
Future<MaterialLocalizations> load(Locale locale) => DefaultMaterialLocalizations.load(locale);
|
|
|
|
@override
|
|
bool shouldReload(MaterialLocalizationsDelegate old) => false;
|
|
}
|
|
|
|
class WidgetsLocalizationsDelegate extends LocalizationsDelegate<WidgetsLocalizations> {
|
|
@override
|
|
bool isSupported(Locale locale) => true;
|
|
|
|
@override
|
|
Future<WidgetsLocalizations> load(Locale locale) => DefaultWidgetsLocalizations.load(locale);
|
|
|
|
@override
|
|
bool shouldReload(WidgetsLocalizationsDelegate old) => false;
|
|
}
|
|
|
|
Widget textFieldBoilerplate({required Widget child}) {
|
|
return MaterialApp(
|
|
home: Localizations(
|
|
locale: const Locale('en', 'US'),
|
|
delegates: <LocalizationsDelegate<dynamic>>[
|
|
WidgetsLocalizationsDelegate(),
|
|
MaterialLocalizationsDelegate(),
|
|
],
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(size: Size(800.0, 600.0)),
|
|
child: Center(child: Material(child: child)),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget primaryScrollControllerBoilerplate({
|
|
required Widget child,
|
|
required ScrollController controller,
|
|
}) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: MediaQuery(
|
|
data: const MediaQueryData(),
|
|
child: PrimaryScrollController(controller: controller, child: child),
|
|
),
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('ListView control test', (WidgetTester tester) async {
|
|
final log = <String>[];
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: ListView(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
children: kStates.map<Widget>((String state) {
|
|
return GestureDetector(
|
|
onTap: () {
|
|
log.add(state);
|
|
},
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
child: Container(height: 200.0, color: const Color(0xFF0000FF), child: Text(state)),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('Alabama'));
|
|
expect(log, equals(<String>['Alabama']));
|
|
log.clear();
|
|
|
|
expect(find.text('Nevada'), findsNothing);
|
|
|
|
await tester.drag(find.text('Alabama'), const Offset(0.0, -4000.0));
|
|
await tester.pump();
|
|
|
|
expect(find.text('Alabama'), findsNothing);
|
|
expect(tester.getCenter(find.text('Massachusetts')), equals(const Offset(400.0, 100.0)));
|
|
|
|
await tester.tap(find.text('Massachusetts'));
|
|
expect(log, equals(<String>['Massachusetts']));
|
|
log.clear();
|
|
});
|
|
|
|
testWidgets('ListView dismiss keyboard onDrag test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: ListView(
|
|
padding: EdgeInsets.zero,
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
children: focusNodes.map((FocusNode focusNode) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNode,
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('GridView.builder supports null items', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView.builder(
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 42),
|
|
itemCount: 42,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
if (index == 5) {
|
|
return null;
|
|
}
|
|
|
|
return const Text('item');
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('item'), findsNWidgets(5));
|
|
});
|
|
|
|
testWidgets('ListView.builder supports null items', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: ListView.builder(
|
|
itemCount: 42,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
if (index == 5) {
|
|
return null;
|
|
}
|
|
|
|
return const Text('item');
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('item'), findsNWidgets(5));
|
|
});
|
|
|
|
testWidgets('PageView supports null items in itemBuilder', (WidgetTester tester) async {
|
|
final controller = PageController(viewportFraction: 1 / 5);
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: PageView.builder(
|
|
itemCount: 5,
|
|
controller: controller,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
if (index == 2) {
|
|
return null;
|
|
}
|
|
|
|
return const Text('item');
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('item'), findsNWidgets(2));
|
|
});
|
|
|
|
testWidgets('ListView.separated supports null items in itemBuilder', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: ListView.separated(
|
|
itemCount: 42,
|
|
separatorBuilder: (BuildContext context, int index) {
|
|
return const Text('separator');
|
|
},
|
|
itemBuilder: (BuildContext context, int index) {
|
|
if (index == 5) {
|
|
return null;
|
|
}
|
|
|
|
return const Text('item');
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('item'), findsNWidgets(5));
|
|
expect(find.text('separator'), findsNWidgets(5));
|
|
});
|
|
|
|
testWidgets('ListView.builder dismiss keyboard onDrag test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: ListView.builder(
|
|
padding: EdgeInsets.zero,
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
itemCount: focusNodes.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNodes[index],
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('ListView.custom dismiss keyboard onDrag test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: ListView.custom(
|
|
padding: EdgeInsets.zero,
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
childrenDelegate: SliverChildBuilderDelegate((BuildContext context, int index) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNodes[index],
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}, childCount: focusNodes.length),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('ListView.separated dismiss keyboard onDrag test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: ListView.separated(
|
|
padding: EdgeInsets.zero,
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
itemCount: focusNodes.length,
|
|
separatorBuilder: (BuildContext context, int index) => const Divider(),
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNodes[index],
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('GridView dismiss keyboard onDrag test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView(
|
|
padding: EdgeInsets.zero,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
children: focusNodes.map((FocusNode focusNode) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNode,
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('GridView.builder dismiss keyboard onDrag test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView.builder(
|
|
padding: EdgeInsets.zero,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
itemCount: focusNodes.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNodes[index],
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('GridView.count dismiss keyboard onDrag test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView.count(
|
|
padding: EdgeInsets.zero,
|
|
crossAxisCount: 2,
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
children: focusNodes.map((FocusNode focusNode) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNode,
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('GridView.extent dismiss keyboard onDrag test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView.extent(
|
|
padding: EdgeInsets.zero,
|
|
maxCrossAxisExtent: 300,
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
children: focusNodes.map((FocusNode focusNode) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNode,
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('GridView.custom dismiss keyboard onDrag test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView.custom(
|
|
padding: EdgeInsets.zero,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
childrenDelegate: SliverChildBuilderDelegate((BuildContext context, int index) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNodes[index],
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}, childCount: focusNodes.length),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('ListView dismiss keyboard manual test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: ListView(
|
|
padding: EdgeInsets.zero,
|
|
children: focusNodes.map((FocusNode focusNode) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNode,
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('ListView.builder dismiss keyboard manual test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: ListView.builder(
|
|
padding: EdgeInsets.zero,
|
|
itemCount: focusNodes.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNodes[index],
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('ListView.custom dismiss keyboard manual test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: ListView.custom(
|
|
padding: EdgeInsets.zero,
|
|
childrenDelegate: SliverChildBuilderDelegate((BuildContext context, int index) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNodes[index],
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}, childCount: focusNodes.length),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('ListView.separated dismiss keyboard manual test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: ListView.separated(
|
|
padding: EdgeInsets.zero,
|
|
itemCount: focusNodes.length,
|
|
separatorBuilder: (BuildContext context, int index) => const Divider(),
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNodes[index],
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('GridView dismiss keyboard manual test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView(
|
|
padding: EdgeInsets.zero,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
|
|
children: focusNodes.map((FocusNode focusNode) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNode,
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('GridView.builder dismiss keyboard manual test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView.builder(
|
|
padding: EdgeInsets.zero,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
|
|
itemCount: focusNodes.length,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNodes[index],
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('GridView.count dismiss keyboard manual test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView.count(
|
|
padding: EdgeInsets.zero,
|
|
crossAxisCount: 2,
|
|
children: focusNodes.map((FocusNode focusNode) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNode,
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('GridView.extent dismiss keyboard manual test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView.extent(
|
|
padding: EdgeInsets.zero,
|
|
maxCrossAxisExtent: 300,
|
|
children: focusNodes.map((FocusNode focusNode) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNode,
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('GridView.custom dismiss keyboard manual test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: GridView.custom(
|
|
padding: EdgeInsets.zero,
|
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
|
|
childrenDelegate: SliverChildBuilderDelegate((BuildContext context, int index) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNodes[index],
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}, childCount: focusNodes.length),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('ListView restart ballistic activity out of range', (WidgetTester tester) async {
|
|
Widget buildListView(int n) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: ListView(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
children: kStates.take(n).map<Widget>((String state) {
|
|
return Container(height: 200.0, color: const Color(0xFF0000FF), child: Text(state));
|
|
}).toList(),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildListView(30));
|
|
await tester.fling(find.byType(ListView), const Offset(0.0, -4000.0), 4000.0);
|
|
await tester.pumpWidget(buildListView(15));
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
await tester.pump(const Duration(milliseconds: 10));
|
|
await tester.pumpAndSettle();
|
|
|
|
final Viewport viewport = tester.widget(find.byType(Viewport));
|
|
expect(viewport.offset.pixels, equals(2400.0));
|
|
});
|
|
|
|
testWidgets('CustomScrollView control test', (WidgetTester tester) async {
|
|
final log = <String>[];
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CustomScrollView(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
slivers: <Widget>[
|
|
SliverList.list(
|
|
children: kStates.map<Widget>((String state) {
|
|
return GestureDetector(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
onTap: () {
|
|
log.add(state);
|
|
},
|
|
child: Container(
|
|
height: 200.0,
|
|
color: const Color(0xFF0000FF),
|
|
child: Text(state),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('Alabama'));
|
|
expect(log, equals(<String>['Alabama']));
|
|
log.clear();
|
|
|
|
expect(find.text('Nevada'), findsNothing);
|
|
|
|
await tester.drag(find.text('Alabama'), const Offset(0.0, -4000.0));
|
|
await tester.pump();
|
|
|
|
expect(find.text('Alabama'), findsNothing);
|
|
expect(tester.getCenter(find.text('Massachusetts')), equals(const Offset(400.0, 100.0)));
|
|
|
|
await tester.tap(find.text('Massachusetts'));
|
|
expect(log, equals(<String>['Massachusetts']));
|
|
log.clear();
|
|
});
|
|
|
|
testWidgets('CustomScrollView dismiss keyboard onDrag test', (WidgetTester tester) async {
|
|
final focusNodes = List<FocusNode>.generate(50, (int i) => FocusNode());
|
|
addTearDown(() {
|
|
for (final node in focusNodes) {
|
|
node.dispose();
|
|
}
|
|
});
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: CustomScrollView(
|
|
dragStartBehavior: DragStartBehavior.down,
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
slivers: <Widget>[
|
|
SliverList.list(
|
|
children: focusNodes.map((FocusNode focusNode) {
|
|
return Container(
|
|
height: 50,
|
|
color: Colors.green,
|
|
child: TextField(
|
|
focusNode: focusNode,
|
|
style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
|
),
|
|
);
|
|
}).toList(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder finder = find.byType(TextField).first;
|
|
final TextField textField = tester.widget(finder);
|
|
await tester.showKeyboard(finder);
|
|
expect(textField.focusNode!.hasFocus, isTrue);
|
|
|
|
await tester.drag(finder, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
expect(textField.focusNode!.hasFocus, isFalse);
|
|
});
|
|
|
|
testWidgets('Can jumpTo during drag', (WidgetTester tester) async {
|
|
final log = <Type>[];
|
|
final controller = ScrollController();
|
|
addTearDown(controller.dispose);
|
|
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: NotificationListener<ScrollNotification>(
|
|
onNotification: (ScrollNotification notification) {
|
|
log.add(notification.runtimeType);
|
|
return false;
|
|
},
|
|
child: ListView(
|
|
controller: controller,
|
|
children: kStates.map<Widget>((String state) {
|
|
return SizedBox(height: 200.0, child: Text(state));
|
|
}).toList(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(log, isEmpty);
|
|
|
|
final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
|
|
await gesture.moveBy(const Offset(0.0, -100.0));
|
|
|
|
expect(
|
|
log,
|
|
equals(<Type>[ScrollStartNotification, UserScrollNotification, ScrollUpdateNotification]),
|
|
);
|
|
log.clear();
|
|
|
|
await tester.pump();
|
|
|
|
controller.jumpTo(550.0);
|
|
|
|
expect(controller.offset, equals(550.0));
|
|
expect(
|
|
log,
|
|
equals(<Type>[
|
|
ScrollEndNotification,
|
|
UserScrollNotification,
|
|
ScrollStartNotification,
|
|
ScrollUpdateNotification,
|
|
ScrollEndNotification,
|
|
]),
|
|
);
|
|
log.clear();
|
|
|
|
await tester.pump();
|
|
await gesture.moveBy(const Offset(0.0, -100.0));
|
|
|
|
expect(controller.offset, equals(550.0));
|
|
expect(log, isEmpty);
|
|
});
|
|
|
|
test(
|
|
'PrimaryScrollController.automaticallyInheritOnPlatforms defaults to all mobile platforms',
|
|
() {
|
|
final controller = ScrollController();
|
|
addTearDown(controller.dispose);
|
|
final primaryScrollController = PrimaryScrollController(
|
|
controller: controller,
|
|
child: const SizedBox(),
|
|
);
|
|
expect(
|
|
primaryScrollController.automaticallyInheritForPlatforms,
|
|
TargetPlatformVariant.mobile().values,
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets('Vertical CustomScrollViews are not primary by default', (WidgetTester tester) async {
|
|
const view = CustomScrollView();
|
|
expect(view.primary, isNull);
|
|
});
|
|
|
|
testWidgets(
|
|
'Vertical CustomScrollViews use PrimaryScrollController by default on mobile',
|
|
(WidgetTester tester) async {
|
|
final controller = ScrollController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(child: const CustomScrollView(), controller: controller),
|
|
);
|
|
expect(controller.hasClients, isTrue);
|
|
},
|
|
variant: TargetPlatformVariant.mobile(),
|
|
);
|
|
|
|
testWidgets(
|
|
"Vertical CustomScrollViews don't use PrimaryScrollController by default on desktop",
|
|
(WidgetTester tester) async {
|
|
final controller = ScrollController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(child: const CustomScrollView(), controller: controller),
|
|
);
|
|
expect(controller.hasClients, isFalse);
|
|
},
|
|
variant: TargetPlatformVariant.desktop(),
|
|
);
|
|
|
|
testWidgets('Vertical ListViews are not primary by default', (WidgetTester tester) async {
|
|
final view = ListView();
|
|
expect(view.primary, isNull);
|
|
});
|
|
|
|
testWidgets(
|
|
'Vertical ListViews use PrimaryScrollController by default on mobile',
|
|
(WidgetTester tester) async {
|
|
final controller = ScrollController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(child: ListView(), controller: controller),
|
|
);
|
|
expect(controller.hasClients, isTrue);
|
|
},
|
|
variant: TargetPlatformVariant.mobile(),
|
|
);
|
|
|
|
testWidgets(
|
|
"Vertical ListViews don't use PrimaryScrollController by default on desktop",
|
|
(WidgetTester tester) async {
|
|
final controller = ScrollController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(child: ListView(), controller: controller),
|
|
);
|
|
expect(controller.hasClients, isFalse);
|
|
},
|
|
variant: TargetPlatformVariant.desktop(),
|
|
);
|
|
|
|
testWidgets('Vertical GridViews are not primary by default', (WidgetTester tester) async {
|
|
final view = GridView.count(crossAxisCount: 1);
|
|
expect(view.primary, isNull);
|
|
});
|
|
|
|
testWidgets(
|
|
'Vertical GridViews use PrimaryScrollController by default on mobile',
|
|
(WidgetTester tester) async {
|
|
final controller = ScrollController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(
|
|
child: GridView.count(crossAxisCount: 1),
|
|
controller: controller,
|
|
),
|
|
);
|
|
expect(controller.hasClients, isTrue);
|
|
},
|
|
variant: TargetPlatformVariant.mobile(),
|
|
);
|
|
|
|
testWidgets(
|
|
"Vertical GridViews don't use PrimaryScrollController by default on desktop",
|
|
(WidgetTester tester) async {
|
|
final controller = ScrollController();
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(
|
|
child: GridView.count(crossAxisCount: 1),
|
|
controller: controller,
|
|
),
|
|
);
|
|
expect(controller.hasClients, isFalse);
|
|
},
|
|
variant: TargetPlatformVariant.desktop(),
|
|
);
|
|
|
|
testWidgets('Horizontal CustomScrollViews are non-primary by default', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final controller1 = ScrollController();
|
|
addTearDown(controller1.dispose);
|
|
final controller2 = ScrollController();
|
|
addTearDown(controller2.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(
|
|
child: CustomScrollView(scrollDirection: Axis.horizontal, controller: controller2),
|
|
controller: controller1,
|
|
),
|
|
);
|
|
expect(controller1.hasClients, isFalse);
|
|
});
|
|
|
|
testWidgets('Horizontal ListViews are non-primary by default', (WidgetTester tester) async {
|
|
final controller1 = ScrollController();
|
|
addTearDown(controller1.dispose);
|
|
final controller2 = ScrollController();
|
|
addTearDown(controller2.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(
|
|
child: ListView(scrollDirection: Axis.horizontal, controller: controller2),
|
|
controller: controller1,
|
|
),
|
|
);
|
|
expect(controller1.hasClients, isFalse);
|
|
});
|
|
|
|
testWidgets('Horizontal GridViews are non-primary by default', (WidgetTester tester) async {
|
|
final controller1 = ScrollController();
|
|
addTearDown(controller1.dispose);
|
|
final controller2 = ScrollController();
|
|
addTearDown(controller2.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(
|
|
child: GridView.count(
|
|
scrollDirection: Axis.horizontal,
|
|
controller: controller2,
|
|
crossAxisCount: 1,
|
|
),
|
|
controller: controller1,
|
|
),
|
|
);
|
|
expect(controller1.hasClients, isFalse);
|
|
});
|
|
|
|
testWidgets('CustomScrollViews with controllers are non-primary by default', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final controller1 = ScrollController();
|
|
addTearDown(controller1.dispose);
|
|
final controller2 = ScrollController();
|
|
addTearDown(controller2.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(
|
|
child: CustomScrollView(controller: controller2),
|
|
controller: controller1,
|
|
),
|
|
);
|
|
expect(controller1.hasClients, isFalse);
|
|
});
|
|
|
|
testWidgets('ListViews with controllers are non-primary by default', (WidgetTester tester) async {
|
|
final controller1 = ScrollController();
|
|
addTearDown(controller1.dispose);
|
|
final controller2 = ScrollController();
|
|
addTearDown(controller2.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(
|
|
child: ListView(controller: controller2),
|
|
controller: controller1,
|
|
),
|
|
);
|
|
expect(controller1.hasClients, isFalse);
|
|
});
|
|
|
|
testWidgets('GridViews with controllers are non-primary by default', (WidgetTester tester) async {
|
|
final controller1 = ScrollController();
|
|
addTearDown(controller1.dispose);
|
|
final controller2 = ScrollController();
|
|
addTearDown(controller2.dispose);
|
|
await tester.pumpWidget(
|
|
primaryScrollControllerBoilerplate(
|
|
child: GridView.count(controller: controller2, crossAxisCount: 1),
|
|
controller: controller1,
|
|
),
|
|
);
|
|
expect(controller1.hasClients, isFalse);
|
|
});
|
|
|
|
testWidgets('CustomScrollView sets PrimaryScrollController when primary', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final primaryScrollController = ScrollController();
|
|
addTearDown(primaryScrollController.dispose);
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PrimaryScrollController(
|
|
controller: primaryScrollController,
|
|
child: const CustomScrollView(primary: true),
|
|
),
|
|
),
|
|
);
|
|
final Scrollable scrollable = tester.widget(find.byType(Scrollable));
|
|
expect(scrollable.controller, primaryScrollController);
|
|
});
|
|
|
|
testWidgets('ListView sets PrimaryScrollController when primary', (WidgetTester tester) async {
|
|
final primaryScrollController = ScrollController();
|
|
addTearDown(primaryScrollController.dispose);
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PrimaryScrollController(
|
|
controller: primaryScrollController,
|
|
child: ListView(primary: true),
|
|
),
|
|
),
|
|
);
|
|
final Scrollable scrollable = tester.widget(find.byType(Scrollable));
|
|
expect(scrollable.controller, primaryScrollController);
|
|
});
|
|
|
|
testWidgets('GridView sets PrimaryScrollController when primary', (WidgetTester tester) async {
|
|
final primaryScrollController = ScrollController();
|
|
addTearDown(primaryScrollController.dispose);
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PrimaryScrollController(
|
|
controller: primaryScrollController,
|
|
child: GridView.count(primary: true, crossAxisCount: 1),
|
|
),
|
|
),
|
|
);
|
|
final Scrollable scrollable = tester.widget(find.byType(Scrollable));
|
|
expect(scrollable.controller, primaryScrollController);
|
|
});
|
|
|
|
testWidgets('Nested scrollables have a null PrimaryScrollController', (
|
|
WidgetTester tester,
|
|
) async {
|
|
const innerKey = Key('inner');
|
|
final primaryScrollController = ScrollController();
|
|
addTearDown(primaryScrollController.dispose);
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: PrimaryScrollController(
|
|
controller: primaryScrollController,
|
|
child: ListView(
|
|
primary: true,
|
|
children: <Widget>[
|
|
Container(
|
|
constraints: const BoxConstraints(maxHeight: 200.0),
|
|
child: ListView(key: innerKey, primary: true),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Scrollable innerScrollable = tester.widget(
|
|
find.descendant(of: find.byKey(innerKey), matching: find.byType(Scrollable)),
|
|
);
|
|
expect(innerScrollable.controller, isNull);
|
|
});
|
|
|
|
testWidgets('Primary ListViews are always scrollable', (WidgetTester tester) async {
|
|
final view = ListView(primary: true);
|
|
expect(view.physics, isA<AlwaysScrollableScrollPhysics>());
|
|
});
|
|
|
|
testWidgets('Non-primary ListViews are not always scrollable', (WidgetTester tester) async {
|
|
final view = ListView(primary: false);
|
|
expect(view.physics, isNot(isA<AlwaysScrollableScrollPhysics>()));
|
|
});
|
|
|
|
testWidgets('Defaulting-to-primary ListViews are always scrollable', (WidgetTester tester) async {
|
|
final view = ListView();
|
|
expect(view.physics, isA<AlwaysScrollableScrollPhysics>());
|
|
});
|
|
|
|
testWidgets('Defaulting-to-not-primary ListViews are not always scrollable', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final view = ListView(scrollDirection: Axis.horizontal);
|
|
expect(view.physics, isNot(isA<AlwaysScrollableScrollPhysics>()));
|
|
});
|
|
|
|
testWidgets('primary:true leads to scrolling', (WidgetTester tester) async {
|
|
var scrolled = false;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: NotificationListener<OverscrollNotification>(
|
|
onNotification: (OverscrollNotification message) {
|
|
scrolled = true;
|
|
return false;
|
|
},
|
|
child: ListView(primary: true),
|
|
),
|
|
),
|
|
);
|
|
await tester.dragFrom(const Offset(100.0, 100.0), const Offset(0.0, 100.0));
|
|
expect(scrolled, isTrue);
|
|
});
|
|
|
|
testWidgets('primary:false leads to no scrolling', (WidgetTester tester) async {
|
|
var scrolled = false;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: NotificationListener<OverscrollNotification>(
|
|
onNotification: (OverscrollNotification message) {
|
|
scrolled = true;
|
|
return false;
|
|
},
|
|
child: ListView(primary: false),
|
|
),
|
|
),
|
|
);
|
|
await tester.dragFrom(const Offset(100.0, 100.0), const Offset(0.0, 100.0));
|
|
expect(scrolled, isFalse);
|
|
});
|
|
|
|
testWidgets(
|
|
'physics:AlwaysScrollableScrollPhysics actually overrides primary:false default behavior',
|
|
(WidgetTester tester) async {
|
|
var scrolled = false;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: NotificationListener<OverscrollNotification>(
|
|
onNotification: (OverscrollNotification message) {
|
|
scrolled = true;
|
|
return false;
|
|
},
|
|
child: ListView(primary: false, physics: const AlwaysScrollableScrollPhysics()),
|
|
),
|
|
),
|
|
);
|
|
await tester.dragFrom(const Offset(100.0, 100.0), const Offset(0.0, 100.0));
|
|
expect(scrolled, isTrue);
|
|
},
|
|
);
|
|
|
|
testWidgets('physics:ScrollPhysics actually overrides primary:true default behavior', (
|
|
WidgetTester tester,
|
|
) async {
|
|
var scrolled = false;
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: NotificationListener<OverscrollNotification>(
|
|
onNotification: (OverscrollNotification message) {
|
|
scrolled = true;
|
|
return false;
|
|
},
|
|
child: ListView(primary: true, physics: const ScrollPhysics()),
|
|
),
|
|
),
|
|
);
|
|
await tester.dragFrom(const Offset(100.0, 100.0), const Offset(0.0, 100.0));
|
|
expect(scrolled, isFalse);
|
|
});
|
|
|
|
testWidgets('separatorBuilder must return something', (WidgetTester tester) async {
|
|
const listOfValues = <String>['ALPHA', 'BETA', 'GAMMA', 'DELTA'];
|
|
|
|
Widget buildFrame(Widget firstSeparator) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: ListView.separated(
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Text(listOfValues[index]);
|
|
},
|
|
separatorBuilder: (BuildContext context, int index) {
|
|
if (index == 0) {
|
|
return firstSeparator;
|
|
} else {
|
|
return const Divider();
|
|
}
|
|
},
|
|
itemCount: listOfValues.length,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// A separatorBuilder that always returns a Divider is fine
|
|
await tester.pumpWidget(buildFrame(const Divider()));
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('when itemBuilder throws, creates Error Widget', (WidgetTester tester) async {
|
|
const listOfValues = <String>['ALPHA', 'BETA', 'GAMMA', 'DELTA'];
|
|
|
|
Widget buildFrame(bool throwOnFirstItem) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: ListView.builder(
|
|
itemBuilder: (BuildContext context, int index) {
|
|
if (index == 0 && throwOnFirstItem) {
|
|
throw Exception('itemBuilder fail');
|
|
}
|
|
return Text(listOfValues[index]);
|
|
},
|
|
itemCount: listOfValues.length,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// When itemBuilder doesn't throw, no ErrorWidget
|
|
await tester.pumpWidget(buildFrame(false));
|
|
expect(tester.takeException(), isNull);
|
|
final Finder finder = find.byType(ErrorWidget);
|
|
expect(find.byType(ErrorWidget), findsNothing);
|
|
|
|
// When it does throw, one error widget is rendered in the item's place
|
|
await tester.pumpWidget(buildFrame(true));
|
|
expect(tester.takeException(), isA<Exception>());
|
|
expect(finder, findsOneWidget);
|
|
});
|
|
|
|
testWidgets('when separatorBuilder throws, creates ErrorWidget', (WidgetTester tester) async {
|
|
const listOfValues = <String>['ALPHA', 'BETA', 'GAMMA', 'DELTA'];
|
|
const key = Key('list');
|
|
|
|
Widget buildFrame(bool throwOnFirstSeparator) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: ListView.separated(
|
|
key: key,
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return Text(listOfValues[index]);
|
|
},
|
|
separatorBuilder: (BuildContext context, int index) {
|
|
if (index == 0 && throwOnFirstSeparator) {
|
|
throw Exception('separatorBuilder fail');
|
|
}
|
|
return const Divider();
|
|
},
|
|
itemCount: listOfValues.length,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// When separatorBuilder doesn't throw, no ErrorWidget
|
|
await tester.pumpWidget(buildFrame(false));
|
|
expect(tester.takeException(), isNull);
|
|
final Finder finder = find.byType(ErrorWidget);
|
|
expect(find.byType(ErrorWidget), findsNothing);
|
|
|
|
// When it does throw, one error widget is rendered in the separator's place
|
|
await tester.pumpWidget(buildFrame(true));
|
|
expect(tester.takeException(), isA<Exception>());
|
|
expect(finder, findsOneWidget);
|
|
});
|
|
|
|
testWidgets('ListView asserts on both non-null itemExtent and prototypeItem', (
|
|
WidgetTester tester,
|
|
) async {
|
|
expect(() => ListView(itemExtent: 100, prototypeItem: const SizedBox()), throwsAssertionError);
|
|
});
|
|
|
|
testWidgets('ListView.builder asserts on negative childCount', (WidgetTester tester) async {
|
|
expect(
|
|
() => ListView.builder(
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return const SizedBox();
|
|
},
|
|
itemCount: -1,
|
|
),
|
|
throwsAssertionError,
|
|
);
|
|
});
|
|
|
|
testWidgets('ListView.builder asserts on negative semanticChildCount', (
|
|
WidgetTester tester,
|
|
) async {
|
|
expect(
|
|
() => ListView.builder(
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return const SizedBox();
|
|
},
|
|
itemCount: 1,
|
|
semanticChildCount: -1,
|
|
),
|
|
throwsAssertionError,
|
|
);
|
|
});
|
|
|
|
testWidgets('ListView.builder asserts on nonsensical childCount/semanticChildCount', (
|
|
WidgetTester tester,
|
|
) async {
|
|
expect(
|
|
() => ListView.builder(
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return const SizedBox();
|
|
},
|
|
itemCount: 1,
|
|
semanticChildCount: 4,
|
|
),
|
|
throwsAssertionError,
|
|
);
|
|
});
|
|
|
|
testWidgets('ListView.builder asserts on both non-null itemExtent and prototypeItem', (
|
|
WidgetTester tester,
|
|
) async {
|
|
expect(
|
|
() => ListView.builder(
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return const SizedBox();
|
|
},
|
|
itemExtent: 100,
|
|
prototypeItem: const SizedBox(),
|
|
),
|
|
throwsAssertionError,
|
|
);
|
|
});
|
|
|
|
testWidgets('ListView.custom asserts on both non-null itemExtent and prototypeItem', (
|
|
WidgetTester tester,
|
|
) async {
|
|
expect(
|
|
() => ListView.custom(
|
|
childrenDelegate: SliverChildBuilderDelegate((BuildContext context, int index) {
|
|
return const SizedBox();
|
|
}),
|
|
itemExtent: 100,
|
|
prototypeItem: const SizedBox(),
|
|
),
|
|
throwsAssertionError,
|
|
);
|
|
});
|
|
|
|
testWidgets('PrimaryScrollController provides fallback ScrollActions', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: CustomScrollView(
|
|
primary: true,
|
|
slivers: List<Widget>.generate(20, (int index) {
|
|
return SliverToBoxAdapter(
|
|
child: Focus(
|
|
autofocus: index == 0,
|
|
child: SizedBox(key: ValueKey<String>('Box $index'), height: 50.0),
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
final ScrollController controller = PrimaryScrollController.of(
|
|
tester.element(find.byType(CustomScrollView)),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(controller.position.pixels, equals(0.0));
|
|
expect(
|
|
tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)),
|
|
equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)),
|
|
);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.pageDown);
|
|
await tester.pumpAndSettle();
|
|
expect(controller.position.pixels, equals(400.0));
|
|
expect(
|
|
tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)),
|
|
equals(const Rect.fromLTRB(0.0, -400.0, 800.0, -350.0)),
|
|
);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.pageUp);
|
|
await tester.pumpAndSettle();
|
|
expect(controller.position.pixels, equals(0.0));
|
|
expect(
|
|
tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false)),
|
|
equals(const Rect.fromLTRB(0.0, 0.0, 800.0, 50.0)),
|
|
);
|
|
});
|
|
|
|
testWidgets('Fallback ScrollActions handle too many positions with error message', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Widget getScrollView() {
|
|
return SizedBox(
|
|
width: 400.0,
|
|
child: CustomScrollView(
|
|
primary: true,
|
|
slivers: List<Widget>.generate(20, (int index) {
|
|
return SliverToBoxAdapter(
|
|
child: Focus(child: SizedBox(key: ValueKey<String>('Box $index'), height: 50.0)),
|
|
);
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(home: Row(children: <Widget>[getScrollView(), getScrollView()])),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
tester.getRect(find.byKey(const ValueKey<String>('Box 0'), skipOffstage: false).first),
|
|
equals(const Rect.fromLTRB(0.0, 0.0, 400.0, 50.0)),
|
|
);
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.pageDown);
|
|
final exception = tester.takeException() as AssertionError;
|
|
expect(exception, isAssertionError);
|
|
expect(
|
|
exception.message,
|
|
contains(
|
|
'A ScrollAction was invoked with the PrimaryScrollController, but '
|
|
'more than one ScrollPosition is attached.',
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('if itemExtent is non-null, children have same extent in the scroll direction', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final numbers = <int>[0, 1, 2];
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return ListView.builder(
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return SizedBox(
|
|
key: ValueKey<int>(numbers[index]),
|
|
// children with different heights
|
|
height: 20 + numbers[index] * 10,
|
|
child: ReorderableDragStartListener(
|
|
index: index,
|
|
child: Text(numbers[index].toString()),
|
|
),
|
|
);
|
|
},
|
|
itemCount: numbers.length,
|
|
itemExtent: 30,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final double item0Height = tester.getSize(find.text('0').hitTestable()).height;
|
|
final double item1Height = tester.getSize(find.text('1').hitTestable()).height;
|
|
final double item2Height = tester.getSize(find.text('2').hitTestable()).height;
|
|
|
|
expect(item0Height, 30.0);
|
|
expect(item1Height, 30.0);
|
|
expect(item2Height, 30.0);
|
|
});
|
|
|
|
testWidgets('if prototypeItem is non-null, children have same extent in the scroll direction', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final numbers = <int>[0, 1, 2];
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return ListView.builder(
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return SizedBox(
|
|
key: ValueKey<int>(numbers[index]),
|
|
// children with different heights
|
|
height: 20 + numbers[index] * 10,
|
|
child: ReorderableDragStartListener(
|
|
index: index,
|
|
child: Text(numbers[index].toString()),
|
|
),
|
|
);
|
|
},
|
|
itemCount: numbers.length,
|
|
prototypeItem: const SizedBox(height: 30, child: Text('3')),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final double item0Height = tester.getSize(find.text('0').hitTestable()).height;
|
|
final double item1Height = tester.getSize(find.text('1').hitTestable()).height;
|
|
final double item2Height = tester.getSize(find.text('2').hitTestable()).height;
|
|
|
|
expect(item0Height, 30.0);
|
|
expect(item1Height, 30.0);
|
|
expect(item2Height, 30.0);
|
|
});
|
|
|
|
testWidgets('ListView dismiss keyboard onDrag and keep dismissed on drawer opened test', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final list = List<int>.generate(50, (int i) => i);
|
|
final scaffoldKey = GlobalKey<ScaffoldState>();
|
|
|
|
await tester.pumpWidget(
|
|
textFieldBoilerplate(
|
|
child: Scaffold(
|
|
key: scaffoldKey,
|
|
drawer: Container(),
|
|
body: Column(
|
|
children: <Widget>[
|
|
const TextField(),
|
|
Expanded(
|
|
child: ListView(
|
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
|
children: list.map((int i) {
|
|
return Container(height: 50);
|
|
}).toList(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.testTextInput.isVisible, isFalse);
|
|
final Finder finder = find.byType(TextField).first;
|
|
await tester.tap(finder);
|
|
expect(tester.testTextInput.isVisible, isTrue);
|
|
|
|
await tester.drag(find.byType(ListView).first, const Offset(0.0, -40.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.testTextInput.isVisible, isFalse);
|
|
scaffoldKey.currentState!.openDrawer();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.testTextInput.isVisible, isFalse);
|
|
});
|
|
|
|
testWidgets('ListView.separated findItemIndexCallback preserves state correctly', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final items = <String>['A', 'B', 'C'];
|
|
|
|
Widget buildFrame(List<String> itemList) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: ListView.separated(
|
|
itemCount: itemList.length,
|
|
findItemIndexCallback: (Key key) {
|
|
final valueKey = key as ValueKey<String>;
|
|
return itemList.indexOf(valueKey.value);
|
|
},
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return ItemWidget(key: ValueKey<String>(itemList[index]), value: itemList[index]);
|
|
},
|
|
separatorBuilder: (BuildContext context, int index) => const Divider(),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Build initial frame
|
|
await tester.pumpWidget(buildFrame(items));
|
|
|
|
final Finder texts = find.byType(Text);
|
|
expect(texts, findsNWidgets(3));
|
|
|
|
// Store all text in list
|
|
final textValues = List<String?>.generate(3, (int index) {
|
|
return (tester.widget(texts.at(index)) as Text).data;
|
|
});
|
|
|
|
await tester.pumpWidget(buildFrame(items));
|
|
await tester.pump();
|
|
|
|
final Finder updatedTexts = find.byType(Text);
|
|
expect(updatedTexts, findsNWidgets(3));
|
|
|
|
final updatedTextValues = List<String?>.generate(3, (int index) {
|
|
return (tester.widget(updatedTexts.at(index)) as Text).data;
|
|
});
|
|
|
|
expect(textValues, updatedTextValues);
|
|
});
|
|
|
|
testWidgets('SliverList.separated findItemIndexCallback preserves state correctly', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final items = <String>['A', 'B', 'C'];
|
|
|
|
Widget buildFrame(List<String> itemList) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: CustomScrollView(
|
|
slivers: <Widget>[
|
|
SliverList.separated(
|
|
itemCount: itemList.length,
|
|
findItemIndexCallback: (Key key) {
|
|
final valueKey = key as ValueKey<String>;
|
|
return itemList.indexOf(valueKey.value);
|
|
},
|
|
itemBuilder: (BuildContext context, int index) {
|
|
return ItemWidget(key: ValueKey<String>(itemList[index]), value: itemList[index]);
|
|
},
|
|
separatorBuilder: (BuildContext context, int index) => const Divider(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Build initial frame
|
|
await tester.pumpWidget(buildFrame(items));
|
|
|
|
final Finder texts = find.byType(Text);
|
|
expect(texts, findsNWidgets(3));
|
|
|
|
// Store all text in list
|
|
final textValues = List<String?>.generate(3, (int index) {
|
|
return (tester.widget(texts.at(index)) as Text).data;
|
|
});
|
|
|
|
await tester.pumpWidget(buildFrame(items));
|
|
await tester.pump();
|
|
|
|
final Finder updatedTexts = find.byType(Text);
|
|
expect(updatedTexts, findsNWidgets(3));
|
|
|
|
final updatedTextValues = List<String?>.generate(3, (int index) {
|
|
return (tester.widget(updatedTexts.at(index)) as Text).data;
|
|
});
|
|
|
|
expect(textValues, updatedTextValues);
|
|
});
|
|
}
|