Unverified Commit d8f36071 authored by xster's avatar xster Committed by GitHub

Add a way to generically go back on both Material and Cupertino scaffold pages (#15602)

* Add a way to generically go back on both Material and Cupertino scaffold pages

* review

* lint

* review

* Fix text assert text
parent 4a2217b3
......@@ -26,9 +26,11 @@ class CommonFinders {
/// Finds [Text] and [EditableText] widgets containing string equal to the
/// `text` argument.
///
/// Example:
/// ## Sample code
///
/// ```dart
/// expect(find.text('Back'), findsOneWidget);
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
......@@ -37,8 +39,9 @@ class CommonFinders {
/// Looks for widgets that contain a [Text] descendant with `text`
/// in it.
///
/// Example:
/// ## Sample code
///
/// ```dart
/// // Suppose you have a button with text 'Update' in it:
/// new Button(
/// child: new Text('Update')
......@@ -46,18 +49,24 @@ class CommonFinders {
///
/// // You can find and tap on it like this:
/// tester.tap(find.widgetWithText(Button, 'Update'));
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
Finder widgetWithText(Type widgetType, String text, { bool skipOffstage: true }) {
return new _WidgetWithTextFinder(widgetType, text, skipOffstage: skipOffstage);
return find.ancestor(
of: find.text(text),
matching: find.byType(widgetType),
);
}
/// Finds widgets by searching for one with a particular [Key].
///
/// Example:
/// ## Sample code
///
/// ```dart
/// expect(find.byKey(backKey), findsOneWidget);
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
......@@ -71,9 +80,11 @@ class CommonFinders {
///
/// The `type` argument must be a subclass of [Widget].
///
/// Example:
/// ## Sample code
///
/// ```dart
/// expect(find.byType(IconButton), findsOneWidget);
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
......@@ -82,14 +93,40 @@ class CommonFinders {
/// Finds [Icon] widgets containing icon data equal to the `icon`
/// argument.
///
/// Example:
/// ## Sample code
///
/// ```dart
/// expect(find.byIcon(Icons.inbox), findsOneWidget);
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
Finder byIcon(IconData icon, { bool skipOffstage: true }) => new _WidgetIconFinder(icon, skipOffstage: skipOffstage);
/// Looks for widgets that contain an [Icon] descendant displaying [IconData]
/// `icon` in it.
///
/// ## Sample code
///
/// ```dart
/// // Suppose you have a button with icon 'arrow_forward' in it:
/// new Button(
/// child: new Icon(Icons.arrow_forward)
/// )
///
/// // You can find and tap on it like this:
/// tester.tap(find.widgetWithIcon(Button, Icons.arrow_forward));
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
Finder widgetWithIcon(Type widgetType, IconData icon, { bool skipOffstage: true }) {
return find.ancestor(
of: find.byIcon(icon),
matching: find.byType(widgetType),
);
}
/// Finds widgets by searching for elements with a particular type.
///
/// This does not do subclass tests, so for example
......@@ -98,9 +135,11 @@ class CommonFinders {
///
/// The `type` argument must be a subclass of [Element].
///
/// Example:
/// ## Sample code
///
/// ```dart
/// expect(find.byElementType(SingleChildRenderObjectElement), findsOneWidget);
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
......@@ -109,8 +148,9 @@ class CommonFinders {
/// Finds widgets whose current widget is the instance given by the
/// argument.
///
/// Example:
/// ## Sample code
///
/// ```dart
/// // Suppose you have a button created like this:
/// Widget myButton = new Button(
/// child: new Text('Update')
......@@ -118,6 +158,7 @@ class CommonFinders {
///
/// // You can find and tap on it like this:
/// tester.tap(find.byWidget(myButton));
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
......@@ -125,12 +166,14 @@ class CommonFinders {
/// Finds widgets using a widget [predicate].
///
/// Example:
/// ## Sample code
///
/// ```dart
/// expect(find.byWidgetPredicate(
/// (Widget widget) => widget is Tooltip && widget.message == 'Back',
/// description: 'widget with tooltip "Back"',
/// ), findsOneWidget);
/// ```
///
/// If [description] is provided, then this uses it as the description of the
/// [Finder] and appears, for example, in the error message when the finder
......@@ -145,9 +188,11 @@ class CommonFinders {
/// Finds Tooltip widgets with the given message.
///
/// Example:
/// ## Sample code
///
/// ```dart
/// expect(find.byTooltip('Back'), findsOneWidget);
/// ```
///
/// If the `skipOffstage` argument is true (the default), then this skips
/// nodes that are [Offstage] or that are from inactive [Route]s.
......@@ -160,8 +205,9 @@ class CommonFinders {
/// Finds widgets using an element [predicate].
///
/// Example:
/// ## Sample code
///
/// ```dart
/// expect(find.byElementPredicate(
/// // finds elements of type SingleChildRenderObjectElement, including
/// // those that are actually subclasses of that type.
......@@ -169,6 +215,7 @@ class CommonFinders {
/// (Element element) => element is SingleChildRenderObjectElement,
/// description: '$SingleChildRenderObjectElement element',
/// ), findsOneWidget);
/// ```
///
/// If [description] is provided, then this uses it as the description of the
/// [Finder] and appears, for example, in the error message when the finder
......@@ -184,11 +231,13 @@ class CommonFinders {
/// Finds widgets that are descendants of the [of] parameter and that match
/// the [matching] parameter.
///
/// Example:
/// ## Sample code
///
/// ```dart
/// expect(find.descendant(
/// of: find.widgetWithText(Row, 'label_1'), matching: find.text('value_1')
/// ), findsOneWidget);
/// ```
///
/// If the [matchRoot] argument is true then the widget(s) specified by [of]
/// will be matched along with the descendants.
......@@ -202,8 +251,9 @@ class CommonFinders {
/// Finds widgets that are ancestors of the [of] parameter and that match
/// the [matching] parameter.
///
/// Example:
/// ## Sample code
///
/// ```dart
/// // Test if a Text widget that contains 'faded' is the
/// // descendant of an Opacity widget with opacity 0.5:
/// expect(
......@@ -215,6 +265,7 @@ class CommonFinders {
/// ).opacity,
/// 0.5
/// );
/// ```
///
/// If the [matchRoot] argument is true then the widget(s) specified by [of]
/// will be matched along with the ancestors.
......@@ -434,40 +485,6 @@ class _TextFinder extends MatchFinder {
}
}
class _WidgetWithTextFinder extends Finder {
_WidgetWithTextFinder(this.widgetType, this.text, { bool skipOffstage: true }) : super(skipOffstage: skipOffstage);
final Type widgetType;
final String text;
@override
String get description => 'type $widgetType with text "$text"';
@override
Iterable<Element> apply(Iterable<Element> candidates) {
return candidates
.map((Element textElement) {
if (textElement.widget is! Text)
return null;
final Text textWidget = textElement.widget;
if (textWidget.data == text) {
try {
textElement.visitAncestorElements((Element element) {
if (element.widget.runtimeType == widgetType)
throw element;
return true;
});
} on Element catch (result) {
return result;
}
}
return null;
})
.where((Element element) => element != null);
}
}
class _KeyFinder extends MatchFinder {
_KeyFinder(this.key, { bool skipOffstage: true }) : super(skipOffstage: skipOffstage);
......
......@@ -4,6 +4,7 @@
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
......@@ -15,6 +16,7 @@ import 'all_elements.dart';
import 'binding.dart';
import 'controller.dart';
import 'finders.dart';
import 'matchers.dart';
import 'test_async_utils.dart';
import 'test_text_input.dart';
......@@ -531,6 +533,21 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
await idle();
});
}
/// Makes an effort to dismiss the current page with a Material [Scaffold] or
/// a [CupertinoPageScaffold].
///
/// Will throw an error if there is no back button in the page.
Future<void> pageBack() async {
Finder backButton = find.byTooltip('Back');
if (backButton.evaluate().isEmpty) {
backButton = find.widgetWithIcon(CupertinoButton, CupertinoIcons.back);
}
expect(backButton, findsOneWidget, reason: 'One back button expected on screen');
await tap(backButton);
}
}
typedef void _TickerDisposeCallback(_TestTicker ticker);
......
......@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/widgets.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
const List<Widget> fooBarTexts = const <Text>[
......@@ -195,7 +196,9 @@ void main() {
expect(failure, isNotNull);
expect(
failure.message,
contains('Actual: ?:<zero widgets with text "bar" that has ancestor(s) with type Column with text "foo"')
contains(
'Actual: ?:<zero widgets with text "bar" that has ancestor(s) with type "Column" which is an ancestor of text "foo"',
),
);
});
});
......@@ -255,7 +258,9 @@ void main() {
expect(failure, isNotNull);
expect(
failure.message,
contains('Actual: ?:<zero widgets with type Column with text "foo" which is an ancestor of text "bar"'),
contains(
'Actual: ?:<zero widgets with type "Column" which is an ancestor of text "foo" which is an ancestor of text "bar"',
),
);
});
......@@ -289,6 +294,94 @@ void main() {
});
});
group('pageBack', (){
testWidgets('fails when there are no back buttons', (WidgetTester tester) async {
await tester.pumpWidget(new Container());
expect(
expectAsync0(tester.pageBack),
throwsA(const isInstanceOf<TestFailure>()),
);
});
testWidgets('successfully taps material back buttons', (WidgetTester tester) async {
await tester.pumpWidget(
new MaterialApp(
home: new Center(
child: new Builder(
builder: (BuildContext context) {
return new RaisedButton(
child: const Text('Next'),
onPressed: () {
Navigator.push<void>(context, new MaterialPageRoute<void>(
builder: (BuildContext context) {
return new Scaffold(
appBar: new 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(
new MaterialApp(
home: new Center(
child: new Builder(
builder: (BuildContext context) {
return new CupertinoButton(
child: const Text('Next'),
onPressed: () {
Navigator.push<void>(context, new CupertinoPageRoute<void>(
builder: (BuildContext context) {
return new CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(
middle: const Text('Page 2'),
),
child: new 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.pump(const Duration(milliseconds: 400));
expect(find.text('Next'), findsOneWidget);
expect(find.text('Page 2'), findsNothing);
});
});
testWidgets('hasRunningAnimations control test', (WidgetTester tester) async {
final AnimationController controller = new AnimationController(
duration: const Duration(seconds: 1),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment