mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00

* Update project.pbxproj files to say Flutter rather than Chromium Also, the templates now have an empty organization so that we don't cause people to give their apps a Flutter copyright. * Update the copyright notice checker to require a standard notice on all files * Update copyrights on Dart files. (This was a mechanical commit.) * Fix weird license headers on Dart files that deviate from our conventions; relicense Shrine. Some were already marked "The Flutter Authors", not clear why. Their dates have been normalized. Some were missing the blank line after the license. Some were randomly different in trivial ways for no apparent reason (e.g. missing the trailing period). * Clean up the copyrights in non-Dart files. (Manual edits.) Also, make sure templates don't have copyrights. * Fix some more ORGANIZATIONNAMEs
744 lines
23 KiB
Dart
744 lines
23 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:async';
|
|
import 'dart:io';
|
|
import 'dart:ui';
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/scheduler.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
// ignore: deprecated_member_use
|
|
import 'package:test_api/test_api.dart' as test_package;
|
|
import 'package:test_api/src/frontend/async_matcher.dart' show AsyncMatcher;
|
|
|
|
const List<Widget> fooBarTexts = <Text>[
|
|
Text('foo', textDirection: TextDirection.ltr),
|
|
Text('bar', textDirection: TextDirection.ltr),
|
|
];
|
|
|
|
void main() {
|
|
group('getSemanticsData', () {
|
|
testWidgets('throws when there are no semantics', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Scaffold(
|
|
body: Text('hello'),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(() => tester.getSemantics(find.text('hello')),
|
|
throwsA(isInstanceOf<StateError>()));
|
|
}, semanticsEnabled: false);
|
|
|
|
testWidgets('throws when there are multiple results from the finder', (WidgetTester tester) async {
|
|
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Row(
|
|
children: const <Widget>[
|
|
Text('hello'),
|
|
Text('hello'),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(() => tester.getSemantics(find.text('hello')),
|
|
throwsA(isInstanceOf<StateError>()));
|
|
semanticsHandle.dispose();
|
|
});
|
|
|
|
testWidgets('Returns the correct SemanticsData', (WidgetTester tester) async {
|
|
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
|
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Container(
|
|
child: OutlineButton(
|
|
onPressed: () { },
|
|
child: const Text('hello'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final SemanticsNode node = tester.getSemantics(find.text('hello'));
|
|
final SemanticsData semantics = node.getSemanticsData();
|
|
expect(semantics.label, 'hello');
|
|
expect(semantics.hasAction(SemanticsAction.tap), true);
|
|
expect(semantics.hasFlag(SemanticsFlag.isButton), true);
|
|
semanticsHandle.dispose();
|
|
});
|
|
|
|
testWidgets('Can enable semantics for tests via semanticsEnabled', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Container(
|
|
child: OutlineButton(
|
|
onPressed: () { },
|
|
child: const Text('hello'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final SemanticsNode node = tester.getSemantics(find.text('hello'));
|
|
final SemanticsData semantics = node.getSemanticsData();
|
|
expect(semantics.label, 'hello');
|
|
expect(semantics.hasAction(SemanticsAction.tap), true);
|
|
expect(semantics.hasFlag(SemanticsFlag.isButton), true);
|
|
}, semanticsEnabled: true);
|
|
|
|
testWidgets('Returns merged SemanticsData', (WidgetTester tester) async {
|
|
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
|
|
const Key key = Key('test');
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Semantics(
|
|
label: 'A',
|
|
child: Semantics(
|
|
label: 'B',
|
|
child: Semantics(
|
|
key: key,
|
|
label: 'C',
|
|
child: Container(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final SemanticsNode node = tester.getSemantics(find.byKey(key));
|
|
final SemanticsData semantics = node.getSemanticsData();
|
|
expect(semantics.label, 'A\nB\nC');
|
|
semanticsHandle.dispose();
|
|
});
|
|
});
|
|
|
|
group('ensureVisible', () {
|
|
testWidgets('scrolls to make widget visible', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: ListView.builder(
|
|
itemCount: 20,
|
|
shrinkWrap: true,
|
|
itemBuilder: (BuildContext context, int i) => ListTile(title: Text('Item $i')),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Make sure widget isn't on screen
|
|
expect(find.text('Item 15', skipOffstage: true), findsNothing);
|
|
|
|
await tester.ensureVisible(find.text('Item 15', skipOffstage: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Item 15', skipOffstage: true), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
group('expectLater', () {
|
|
testWidgets('completes when matcher completes', (WidgetTester tester) async {
|
|
final Completer<void> completer = Completer<void>();
|
|
final Future<void> future = expectLater(null, FakeMatcher(completer));
|
|
String result;
|
|
future.then<void>((void value) {
|
|
result = '123';
|
|
});
|
|
test_package.expect(result, isNull);
|
|
completer.complete();
|
|
test_package.expect(result, isNull);
|
|
await future;
|
|
await tester.pump();
|
|
test_package.expect(result, '123');
|
|
});
|
|
|
|
testWidgets('respects the skip flag', (WidgetTester tester) async {
|
|
final Completer<void> completer = Completer<void>();
|
|
final Future<void> future = expectLater(null, FakeMatcher(completer), skip: 'testing skip');
|
|
bool completed = false;
|
|
future.then<void>((_) {
|
|
completed = true;
|
|
});
|
|
test_package.expect(completed, isFalse);
|
|
await future;
|
|
test_package.expect(completed, isTrue);
|
|
});
|
|
});
|
|
|
|
group('findsOneWidget', () {
|
|
testWidgets('finds exactly one widget', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
|
|
expect(find.text('foo'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('fails with a descriptive message', (WidgetTester tester) async {
|
|
TestFailure failure;
|
|
try {
|
|
expect(find.text('foo', skipOffstage: false), findsOneWidget);
|
|
} on TestFailure catch (e) {
|
|
failure = e;
|
|
}
|
|
|
|
expect(failure, isNotNull);
|
|
final String message = failure.message;
|
|
expect(message, contains('Expected: exactly one matching node in the widget tree\n'));
|
|
expect(message, contains('Actual: _TextFinder:<zero widgets with text "foo">\n'));
|
|
expect(message, contains('Which: means none were found but one was expected\n'));
|
|
});
|
|
});
|
|
|
|
group('findsNothing', () {
|
|
testWidgets('finds no widgets', (WidgetTester tester) async {
|
|
expect(find.text('foo'), findsNothing);
|
|
});
|
|
|
|
testWidgets('fails with a descriptive message', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
|
|
|
|
TestFailure failure;
|
|
try {
|
|
expect(find.text('foo', skipOffstage: false), findsNothing);
|
|
} on TestFailure catch (e) {
|
|
failure = e;
|
|
}
|
|
|
|
expect(failure, isNotNull);
|
|
final String message = failure.message;
|
|
|
|
expect(message, contains('Expected: no matching nodes in the widget tree\n'));
|
|
expect(message, contains('Actual: _TextFinder:<exactly one widget with text "foo": Text("foo", textDirection: ltr)>\n'));
|
|
expect(message, contains('Which: means one was found but none were expected\n'));
|
|
});
|
|
|
|
testWidgets('fails with a descriptive message when skipping', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
|
|
|
|
TestFailure failure;
|
|
try {
|
|
expect(find.text('foo'), findsNothing);
|
|
} on TestFailure catch (e) {
|
|
failure = e;
|
|
}
|
|
|
|
expect(failure, isNotNull);
|
|
final String message = failure.message;
|
|
|
|
expect(message, contains('Expected: no matching nodes in the widget tree\n'));
|
|
expect(message, contains('Actual: _TextFinder:<exactly one widget with text "foo" (ignoring offstage widgets): Text("foo", textDirection: ltr)>\n'));
|
|
expect(message, contains('Which: means one was found but none were expected\n'));
|
|
});
|
|
|
|
testWidgets('pumping', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
|
|
int count;
|
|
|
|
final AnimationController test = AnimationController(
|
|
duration: const Duration(milliseconds: 5100),
|
|
vsync: tester,
|
|
);
|
|
count = await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
expect(count, 1); // it always pumps at least one frame
|
|
|
|
test.forward(from: 0.0);
|
|
count = await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
// 1 frame at t=0, starting the animation
|
|
// 1 frame at t=1
|
|
// 1 frame at t=2
|
|
// 1 frame at t=3
|
|
// 1 frame at t=4
|
|
// 1 frame at t=5
|
|
// 1 frame at t=6, ending the animation
|
|
expect(count, 7);
|
|
|
|
test.forward(from: 0.0);
|
|
await tester.pump(); // starts the animation
|
|
count = await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
expect(count, 6);
|
|
|
|
test.forward(from: 0.0);
|
|
await tester.pump(); // starts the animation
|
|
await tester.pump(); // has no effect
|
|
count = await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
expect(count, 6);
|
|
});
|
|
});
|
|
|
|
group('find.byElementPredicate', () {
|
|
testWidgets('fails with a custom description in the message', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
|
|
|
|
const String customDescription = 'custom description';
|
|
TestFailure failure;
|
|
try {
|
|
expect(find.byElementPredicate((_) => false, description: customDescription), findsOneWidget);
|
|
} on TestFailure catch (e) {
|
|
failure = e;
|
|
}
|
|
|
|
expect(failure, isNotNull);
|
|
expect(failure.message, contains('Actual: _ElementPredicateFinder:<zero widgets with $customDescription'));
|
|
});
|
|
});
|
|
|
|
group('find.byWidgetPredicate', () {
|
|
testWidgets('fails with a custom description in the message', (WidgetTester tester) async {
|
|
await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
|
|
|
|
const String customDescription = 'custom description';
|
|
TestFailure failure;
|
|
try {
|
|
expect(find.byWidgetPredicate((_) => false, description: customDescription), findsOneWidget);
|
|
} on TestFailure catch (e) {
|
|
failure = e;
|
|
}
|
|
|
|
expect(failure, isNotNull);
|
|
expect(failure.message, contains('Actual: _WidgetPredicateFinder:<zero widgets with $customDescription'));
|
|
});
|
|
});
|
|
|
|
group('find.descendant', () {
|
|
testWidgets('finds one descendant', (WidgetTester tester) async {
|
|
await tester.pumpWidget(Row(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Column(children: fooBarTexts),
|
|
],
|
|
));
|
|
|
|
expect(find.descendant(
|
|
of: find.widgetWithText(Row, 'foo'),
|
|
matching: find.text('bar'),
|
|
), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('finds two descendants with different ancestors', (WidgetTester tester) async {
|
|
await tester.pumpWidget(Row(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Column(children: fooBarTexts),
|
|
Column(children: fooBarTexts),
|
|
],
|
|
));
|
|
|
|
expect(find.descendant(
|
|
of: find.widgetWithText(Column, 'foo'),
|
|
matching: find.text('bar'),
|
|
), findsNWidgets(2));
|
|
});
|
|
|
|
testWidgets('fails with a descriptive message', (WidgetTester tester) async {
|
|
await tester.pumpWidget(Row(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Column(children: const <Text>[Text('foo', textDirection: TextDirection.ltr)]),
|
|
const Text('bar', textDirection: TextDirection.ltr),
|
|
],
|
|
));
|
|
|
|
TestFailure failure;
|
|
try {
|
|
expect(find.descendant(
|
|
of: find.widgetWithText(Column, 'foo'),
|
|
matching: find.text('bar'),
|
|
), findsOneWidget);
|
|
} on TestFailure catch (e) {
|
|
failure = e;
|
|
}
|
|
|
|
expect(failure, isNotNull);
|
|
expect(
|
|
failure.message,
|
|
contains(
|
|
'Actual: _DescendantFinder:<zero widgets with text "bar" that has ancestor(s) with type "Column" which is an ancestor of text "foo"',
|
|
),
|
|
);
|
|
});
|
|
});
|
|
|
|
group('find.ancestor', () {
|
|
testWidgets('finds one ancestor', (WidgetTester tester) async {
|
|
await tester.pumpWidget(Row(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Column(children: fooBarTexts),
|
|
],
|
|
));
|
|
|
|
expect(find.ancestor(
|
|
of: find.text('bar'),
|
|
matching: find.widgetWithText(Row, 'foo'),
|
|
), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('finds two matching ancestors, one descendant', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Row(
|
|
children: <Widget>[
|
|
Row(children: fooBarTexts),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.ancestor(
|
|
of: find.text('bar'),
|
|
matching: find.byType(Row),
|
|
), findsNWidgets(2));
|
|
});
|
|
|
|
testWidgets('fails with a descriptive message', (WidgetTester tester) async {
|
|
await tester.pumpWidget(Row(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Column(children: const <Text>[Text('foo', textDirection: TextDirection.ltr)]),
|
|
const Text('bar', textDirection: TextDirection.ltr),
|
|
],
|
|
));
|
|
|
|
TestFailure failure;
|
|
try {
|
|
expect(find.ancestor(
|
|
of: find.text('bar'),
|
|
matching: find.widgetWithText(Column, 'foo'),
|
|
), findsOneWidget);
|
|
} on TestFailure catch (e) {
|
|
failure = e;
|
|
}
|
|
|
|
expect(failure, isNotNull);
|
|
expect(
|
|
failure.message,
|
|
contains(
|
|
'Actual: _AncestorFinder:<zero widgets with type "Column" which is an ancestor of text "foo" which is an ancestor of text "bar"',
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Root not matched by default', (WidgetTester tester) async {
|
|
await tester.pumpWidget(Row(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Column(children: fooBarTexts),
|
|
],
|
|
));
|
|
|
|
expect(find.ancestor(
|
|
of: find.byType(Column),
|
|
matching: find.widgetWithText(Column, 'foo'),
|
|
), findsNothing);
|
|
});
|
|
|
|
testWidgets('Match the root', (WidgetTester tester) async {
|
|
await tester.pumpWidget(Row(
|
|
textDirection: TextDirection.ltr,
|
|
children: <Widget>[
|
|
Column(children: fooBarTexts),
|
|
],
|
|
));
|
|
|
|
expect(find.descendant(
|
|
of: find.byType(Column),
|
|
matching: find.widgetWithText(Column, 'foo'),
|
|
matchRoot: true,
|
|
), findsOneWidget);
|
|
});
|
|
});
|
|
|
|
group('pageBack', () {
|
|
testWidgets('fails when there are no back buttons', (WidgetTester tester) async {
|
|
await tester.pumpWidget(Container());
|
|
|
|
expect(
|
|
expectAsync0(tester.pageBack),
|
|
throwsA(isInstanceOf<TestFailure>()),
|
|
);
|
|
});
|
|
|
|
testWidgets('successfully taps material back buttons', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return RaisedButton(
|
|
child: const Text('Next'),
|
|
onPressed: () {
|
|
Navigator.push<void>(context, MaterialPageRoute<void>(
|
|
builder: (BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Page 2'),
|
|
),
|
|
);
|
|
},
|
|
));
|
|
},
|
|
);
|
|
} ,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('Next'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
|
|
await tester.pageBack();
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
|
|
expect(find.text('Next'), findsOneWidget);
|
|
expect(find.text('Page 2'), findsNothing);
|
|
});
|
|
|
|
testWidgets('successfully taps cupertino back buttons', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return CupertinoButton(
|
|
child: const Text('Next'),
|
|
onPressed: () {
|
|
Navigator.push<void>(context, CupertinoPageRoute<void>(
|
|
builder: (BuildContext context) {
|
|
return CupertinoPageScaffold(
|
|
navigationBar: const CupertinoNavigationBar(
|
|
middle: Text('Page 2'),
|
|
),
|
|
child: Container(),
|
|
);
|
|
},
|
|
));
|
|
},
|
|
);
|
|
} ,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('Next'));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 400));
|
|
|
|
await tester.pageBack();
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(find.text('Next'), findsOneWidget);
|
|
expect(find.text('Page 2'), findsNothing);
|
|
});
|
|
});
|
|
|
|
testWidgets('hasRunningAnimations control test', (WidgetTester tester) async {
|
|
final AnimationController controller = AnimationController(
|
|
duration: const Duration(seconds: 1),
|
|
vsync: const TestVSync(),
|
|
);
|
|
expect(tester.hasRunningAnimations, isFalse);
|
|
controller.forward();
|
|
expect(tester.hasRunningAnimations, isTrue);
|
|
controller.stop();
|
|
expect(tester.hasRunningAnimations, isFalse);
|
|
controller.forward();
|
|
expect(tester.hasRunningAnimations, isTrue);
|
|
await tester.pumpAndSettle();
|
|
expect(tester.hasRunningAnimations, isFalse);
|
|
});
|
|
|
|
testWidgets('pumpAndSettle control test', (WidgetTester tester) async {
|
|
final AnimationController controller = AnimationController(
|
|
duration: const Duration(minutes: 525600),
|
|
vsync: const TestVSync(),
|
|
);
|
|
expect(await tester.pumpAndSettle(), 1);
|
|
controller.forward();
|
|
try {
|
|
await tester.pumpAndSettle();
|
|
expect(true, isFalse);
|
|
} catch (e) {
|
|
expect(e, isFlutterError);
|
|
}
|
|
controller.stop();
|
|
expect(await tester.pumpAndSettle(), 1);
|
|
controller.duration = const Duration(seconds: 1);
|
|
controller.forward();
|
|
expect(await tester.pumpAndSettle(const Duration(milliseconds: 300)), 5); // 0, 300, 600, 900, 1200ms
|
|
});
|
|
|
|
group('runAsync', () {
|
|
testWidgets('works with no async calls', (WidgetTester tester) async {
|
|
String value;
|
|
await tester.runAsync(() async {
|
|
value = '123';
|
|
});
|
|
expect(value, '123');
|
|
});
|
|
|
|
testWidgets('works with real async calls', (WidgetTester tester) async {
|
|
final StringBuffer buf = StringBuffer('1');
|
|
await tester.runAsync(() async {
|
|
buf.write('2');
|
|
//ignore: avoid_slow_async_io
|
|
await Directory.current.stat();
|
|
buf.write('3');
|
|
});
|
|
buf.write('4');
|
|
expect(buf.toString(), '1234');
|
|
});
|
|
|
|
testWidgets('propagates return values', (WidgetTester tester) async {
|
|
final String value = await tester.runAsync<String>(() async {
|
|
return '123';
|
|
});
|
|
expect(value, '123');
|
|
});
|
|
|
|
testWidgets('reports errors via framework', (WidgetTester tester) async {
|
|
final String value = await tester.runAsync<String>(() async {
|
|
throw ArgumentError();
|
|
});
|
|
expect(value, isNull);
|
|
expect(tester.takeException(), isArgumentError);
|
|
});
|
|
|
|
testWidgets('disallows re-entry', (WidgetTester tester) async {
|
|
final Completer<void> completer = Completer<void>();
|
|
tester.runAsync<void>(() => completer.future);
|
|
expect(() => tester.runAsync(() async { }), throwsA(isInstanceOf<TestFailure>()));
|
|
completer.complete();
|
|
});
|
|
|
|
testWidgets('maintains existing zone values', (WidgetTester tester) async {
|
|
final Object key = Object();
|
|
await runZoned<Future<void>>(() {
|
|
expect(Zone.current[key], 'abczed');
|
|
return tester.runAsync<void>(() async {
|
|
expect(Zone.current[key], 'abczed');
|
|
});
|
|
}, zoneValues: <dynamic, dynamic>{
|
|
key: 'abczed',
|
|
});
|
|
});
|
|
});
|
|
|
|
testWidgets('showKeyboard can be called twice', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: TextFormField(),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.showKeyboard(find.byType(TextField));
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await tester.pump();
|
|
await tester.showKeyboard(find.byType(TextField));
|
|
await tester.testTextInput.receiveAction(TextInputAction.done);
|
|
await tester.pump();
|
|
await tester.showKeyboard(find.byType(TextField));
|
|
await tester.showKeyboard(find.byType(TextField));
|
|
await tester.pump();
|
|
});
|
|
testWidgets('verifyTickersWereDisposed control test', (WidgetTester tester) async {
|
|
FlutterError error;
|
|
final Ticker ticker = tester.createTicker((Duration duration) {});
|
|
ticker.start();
|
|
try {
|
|
tester.verifyTickersWereDisposed('');
|
|
} on FlutterError catch (e) {
|
|
error = e;
|
|
} finally {
|
|
expect(error, isNotNull);
|
|
expect(error.diagnostics.length, 4);
|
|
expect(error.diagnostics[2].level, DiagnosticLevel.hint);
|
|
expect(
|
|
error.diagnostics[2].toStringDeep(),
|
|
'Tickers used by AnimationControllers should be disposed by\n'
|
|
'calling dispose() on the AnimationController itself. Otherwise,\n'
|
|
'the ticker will leak.\n',
|
|
);
|
|
expect(error.diagnostics.last, isInstanceOf<DiagnosticsProperty<Ticker>>());
|
|
expect(error.diagnostics.last.value, ticker);
|
|
expect(error.toStringDeep(), startsWith(
|
|
'FlutterError\n'
|
|
' A Ticker was active .\n'
|
|
' All Tickers must be disposed.\n'
|
|
' Tickers used by AnimationControllers should be disposed by\n'
|
|
' calling dispose() on the AnimationController itself. Otherwise,\n'
|
|
' the ticker will leak.\n'
|
|
' The offending ticker was:\n'
|
|
' _TestTicker()\n',
|
|
));
|
|
}
|
|
ticker.stop();
|
|
});
|
|
|
|
}
|
|
|
|
class FakeMatcher extends AsyncMatcher {
|
|
FakeMatcher(this.completer);
|
|
|
|
final Completer<void> completer;
|
|
|
|
@override
|
|
Future<String> matchAsync(dynamic object) {
|
|
return completer.future.then<String>((void value) {
|
|
return object?.toString();
|
|
});
|
|
}
|
|
|
|
@override
|
|
Description describe(Description description) => description.add('--fake--');
|
|
}
|
|
|
|
class _SingleTickerTest extends StatefulWidget {
|
|
const _SingleTickerTest({Key key}) : super(key: key);
|
|
|
|
@override
|
|
_SingleTickerTestState createState() => _SingleTickerTestState();
|
|
}
|
|
|
|
class _SingleTickerTestState extends State<_SingleTickerTest> with SingleTickerProviderStateMixin {
|
|
AnimationController controller;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
controller = AnimationController(
|
|
vsync: this,
|
|
duration: const Duration(seconds: 100),
|
|
) ;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container();
|
|
}
|
|
}
|