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

* Refactor widget test framework Instead of: ```dart test("Card Collection smoke test", () { testWidgets((WidgetTester tester) { ``` ...you now say: ```dart testWidgets("Card Collection smoke test", (WidgetTester tester) { ``` Instead of: ```dart expect(tester, hasWidget(find.text('hello'))); ``` ...you now say: ```dart expect(find.text('hello'), findsOneWidget); ``` Instead of the previous API (exists, widgets, widget, stateOf, elementOf, etc), you now have the following comprehensive API. All these are functions that take a Finder, except the all* properties. * `any()` - true if anything matches, c.f. `Iterable.any` * `allWidgets` - all the widgets in the tree * `widget()` - the one and only widget that matches the finder * `firstWidget()` - the first widget that matches the finder * `allElements` - all the elements in the tree * `element()` - the one and only element that matches the finder * `firstElement()` - the first element that matches the finder * `allStates` - all the `State`s in the tree * `state()` - the one and only state that matches the finder * `firstState()` - the first state that matches the finder * `allRenderObjects` - all the render objects in the tree * `renderObject()` - the one and only render object that matches the finder * `firstRenderObject()` - the first render object that matches the finder There's also `layers' which returns the list of current layers. `tap`, `fling`, getCenter, getSize, etc, take Finders, like the APIs above, and expect there to only be one matching widget. The finders are: * `find.text(String text)` * `find.widgetWithText(Type widgetType, String text)` * `find.byKey(Key key)` * `find.byType(Type type)` * `find.byElementType(Type type)` * `find.byConfig(Widget config)` * `find.byWidgetPredicate(WidgetPredicate predicate)` * `find.byElementPredicate(ElementPredicate predicate)` The matchers (for `expect`) are: * `findsNothing` * `findsWidgets` * `findsOneWidget` * `findsNWidgets(n)` * `isOnStage` * `isOffStage` * `isInCard` * `isNotInCard` Benchmarks now use benchmarkWidgets instead of testWidgets. Also, for those of you using mockers, `serviceMocker` now automatically handles the binding initialization. This patch also: * changes how tests are run so that we can more easily swap the logic out for a "real" mode instead of FakeAsync. * introduces CachingIterable. * changes how flutter_driver interacts with the widget tree to use the aforementioned new API rather than ElementTreeTester, which is gone. * removes ElementTreeTester. * changes the semantics of a test for scrollables because we couldn't convince ourselves that the old semantics made sense; it only worked before because flushing the microtasks after every event was broken. * fixes the flushing of microtasks after every event. * Reindent the tests * Fix review comments
185 lines
5.6 KiB
Dart
185 lines
5.6 KiB
Dart
// Copyright 2016 The Chromium Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:test/test.dart';
|
|
|
|
import 'finders.dart';
|
|
|
|
/// Asserts that the [Finder] matches no widgets in the widget tree.
|
|
///
|
|
/// Example:
|
|
///
|
|
/// expect(find.text('Save'), findsNothing);
|
|
const Matcher findsNothing = const _FindsWidgetMatcher(null, 0);
|
|
|
|
/// Asserts that the [Finder] locates at least one widget in the widget tree.
|
|
///
|
|
/// Example:
|
|
///
|
|
/// expect(find.text('Save'), findsWidgets);
|
|
const Matcher findsWidgets = const _FindsWidgetMatcher(1, null);
|
|
|
|
/// Asserts that the [Finder] locates at exactly one widget in the widget tree.
|
|
///
|
|
/// Example:
|
|
///
|
|
/// expect(find.text('Save'), findsOneWidget);
|
|
const Matcher findsOneWidget = const _FindsWidgetMatcher(1, 1);
|
|
|
|
/// Asserts that the [Finder] locates the specified number of widgets in the widget tree.
|
|
///
|
|
/// Example:
|
|
///
|
|
/// expect(find.text('Save'), findsNWidgets(2));
|
|
Matcher findsNWidgets(int n) => new _FindsWidgetMatcher(n, n);
|
|
|
|
/// Asserts that the [Finder] locates the a single widget that has at
|
|
/// least one [OffStage] widget ancestor.
|
|
const Matcher isOffStage = const _IsOffStage();
|
|
|
|
/// Asserts that the [Finder] locates the a single widget that has no
|
|
/// [OffStage] widget ancestors.
|
|
const Matcher isOnStage = const _IsOnStage();
|
|
|
|
/// Asserts that the [Finder] locates the a single widget that has at
|
|
/// least one [Card] widget ancestor.
|
|
const Matcher isInCard = const _IsInCard();
|
|
|
|
/// Asserts that the [Finder] locates the a single widget that has no
|
|
/// [Card] widget ancestors.
|
|
const Matcher isNotInCard = const _IsNotInCard();
|
|
|
|
class _FindsWidgetMatcher extends Matcher {
|
|
const _FindsWidgetMatcher(this.min, this.max);
|
|
|
|
final int min;
|
|
final int max;
|
|
|
|
@override
|
|
bool matches(Finder finder, Map<dynamic, dynamic> matchState) {
|
|
assert(min != null || max != null);
|
|
matchState[Finder] = finder;
|
|
if (min != null) {
|
|
int count = 0;
|
|
Iterator<Element> iterator = finder.evaluate().iterator;
|
|
while (count < min && iterator.moveNext())
|
|
count += 1;
|
|
if (count < min)
|
|
return false;
|
|
}
|
|
if (max != null) {
|
|
int count = 0;
|
|
Iterator<Element> iterator = finder.evaluate().iterator;
|
|
while (count <= max && iterator.moveNext())
|
|
count += 1;
|
|
if (count > max)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Description describe(Description description) {
|
|
assert(min != null || max != null);
|
|
if (min == max) {
|
|
if (min == 1)
|
|
return description.add('exactly one matching node in the widget tree');
|
|
return description.add('exactly $min matching nodes in the widget tree');
|
|
}
|
|
if (min == null) {
|
|
if (max == 0)
|
|
return description.add('no matching nodes in the widget tree');
|
|
if (max == 1)
|
|
return description.add('at most one matching node in the widget tree');
|
|
return description.add('at most $max matching nodes in the widget tree');
|
|
}
|
|
if (max == null) {
|
|
if (min == 1)
|
|
return description.add('at least one matching node in the widget tree');
|
|
return description.add('at least $min matching nodes in the widget tree');
|
|
}
|
|
return description.add('between $min and $max matching nodes in the widget tree (inclusive)');
|
|
}
|
|
|
|
@override
|
|
Description describeMismatch(
|
|
dynamic item,
|
|
Description mismatchDescription,
|
|
Map<dynamic, dynamic> matchState,
|
|
bool verbose
|
|
) {
|
|
Finder finder = matchState[Finder];
|
|
int count = finder.evaluate().length;
|
|
if (count == 0) {
|
|
assert(min != null && min > 0);
|
|
if (min == 1 && max == 1)
|
|
return mismatchDescription.add('means none were found but one was expected');
|
|
return mismatchDescription.add('means none were found but some were expected');
|
|
}
|
|
if (max == 0) {
|
|
if (count == 1)
|
|
return mismatchDescription.add('means one was found but none were expected');
|
|
return mismatchDescription.add('means some were found but none were expected');
|
|
}
|
|
if (min != null && count < min)
|
|
return mismatchDescription.add('is not enough');
|
|
assert(max != null && count > min);
|
|
return mismatchDescription.add('is too many');
|
|
}
|
|
}
|
|
|
|
bool _hasAncestorOfType(Finder finder, Type targetType) {
|
|
expect(finder, findsOneWidget);
|
|
bool result = false;
|
|
finder.evaluate().single.visitAncestorElements((Element ancestor) {
|
|
if (ancestor.widget.runtimeType == targetType) {
|
|
result = true;
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
class _IsOffStage extends Matcher {
|
|
const _IsOffStage();
|
|
|
|
@override
|
|
bool matches(Finder finder, Map<dynamic, dynamic> matchState) => _hasAncestorOfType(finder, OffStage);
|
|
|
|
@override
|
|
Description describe(Description description) => description.add('offstage');
|
|
}
|
|
|
|
class _IsOnStage extends Matcher {
|
|
const _IsOnStage();
|
|
|
|
@override
|
|
bool matches(Finder finder, Map<dynamic, dynamic> matchState) => !_hasAncestorOfType(finder, OffStage);
|
|
|
|
@override
|
|
Description describe(Description description) => description.add('onstage');
|
|
}
|
|
|
|
class _IsInCard extends Matcher {
|
|
const _IsInCard();
|
|
|
|
@override
|
|
bool matches(Finder finder, Map<dynamic, dynamic> matchState) => _hasAncestorOfType(finder, Card);
|
|
|
|
@override
|
|
Description describe(Description description) => description.add('in card');
|
|
}
|
|
|
|
class _IsNotInCard extends Matcher {
|
|
const _IsNotInCard();
|
|
|
|
@override
|
|
bool matches(Finder finder, Map<dynamic, dynamic> matchState) => !_hasAncestorOfType(finder, Card);
|
|
|
|
@override
|
|
Description describe(Description description) => description.add('not in card');
|
|
}
|