blob: 1c4342b3d163adcd7df0d60816fe68ea6daf4cf3 [file] [log] [blame]
// 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:io';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('image', () {
testWidgets('finds Image widgets', (WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(
Image(image: FileImage(File('test')))
));
expect(find.image(FileImage(File('test'))), findsOneWidget);
});
testWidgets('finds Button widgets with Image', (WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(
ElevatedButton(onPressed: null, child: Image(image: FileImage(File('test'))),)
));
expect(find.widgetWithImage(ElevatedButton, FileImage(File('test'))), findsOneWidget);
});
});
group('text', () {
testWidgets('finds Text widgets', (WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(
const Text('test'),
));
expect(find.text('test'), findsOneWidget);
});
testWidgets('finds Text.rich widgets', (WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(
const Text.rich(
TextSpan(text: 't', children: <TextSpan>[
TextSpan(text: 'e'),
TextSpan(text: 'st'),
],
),
)));
expect(find.text('test'), findsOneWidget);
});
group('findRichText', () {
testWidgets('finds RichText widgets when enabled',
(WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(RichText(
text: const TextSpan(
text: 't',
children: <TextSpan>[
TextSpan(text: 'est'),
],
),
)));
expect(find.text('test', findRichText: true), findsOneWidget);
});
testWidgets('finds Text widgets once when enabled',
(WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(const Text('test2')));
expect(find.text('test2', findRichText: true), findsOneWidget);
});
testWidgets('does not find RichText widgets when disabled',
(WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(RichText(
text: const TextSpan(
text: 't',
children: <TextSpan>[
TextSpan(text: 'est'),
],
),
)));
expect(find.text('test'), findsNothing);
});
testWidgets(
'does not find Text and RichText separated by semantics widgets twice',
(WidgetTester tester) async {
// If rich: true found both Text and RichText, this would find two widgets.
await tester.pumpWidget(_boilerplate(
const Text('test', semanticsLabel: 'foo'),
));
expect(find.text('test'), findsOneWidget);
});
testWidgets('finds Text.rich widgets when enabled',
(WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(const Text.rich(
TextSpan(
text: 't',
children: <TextSpan>[
TextSpan(text: 'est'),
TextSpan(text: '3'),
],
),
)));
expect(find.text('test3', findRichText: true), findsOneWidget);
});
testWidgets('finds Text.rich widgets when disabled',
(WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(const Text.rich(
TextSpan(
text: 't',
children: <TextSpan>[
TextSpan(text: 'est'),
TextSpan(text: '3'),
],
),
)));
expect(find.text('test3'), findsOneWidget);
});
});
});
group('textContaining', () {
testWidgets('finds Text widgets', (WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(
const Text('this is a test'),
));
expect(find.textContaining(RegExp(r'test')), findsOneWidget);
expect(find.textContaining('test'), findsOneWidget);
expect(find.textContaining('a'), findsOneWidget);
expect(find.textContaining('s'), findsOneWidget);
});
testWidgets('finds Text.rich widgets', (WidgetTester tester) async {
await tester.pumpWidget(_boilerplate(
const Text.rich(
TextSpan(text: 'this', children: <TextSpan>[
TextSpan(text: 'is'),
TextSpan(text: 'a'),
TextSpan(text: 'test'),
],
),
)));
expect(find.textContaining(RegExp(r'isatest')), findsOneWidget);
expect(find.textContaining('isatest'), findsOneWidget);
});
testWidgets('finds EditableText widgets', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: _boilerplate(TextField(
controller: TextEditingController()..text = 'this is test',
)),
),
));
expect(find.textContaining(RegExp(r'test')), findsOneWidget);
expect(find.textContaining('test'), findsOneWidget);
});
});
group('semantics', () {
testWidgets('Throws StateError if semantics are not enabled', (WidgetTester tester) async {
expect(() => find.bySemanticsLabel('Add'), throwsStateError);
}, semanticsEnabled: false);
testWidgets('finds Semantically labeled widgets', (WidgetTester tester) async {
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
await tester.pumpWidget(_boilerplate(
Semantics(
label: 'Add',
button: true,
child: const TextButton(
onPressed: null,
child: Text('+'),
),
),
));
expect(find.bySemanticsLabel('Add'), findsOneWidget);
semanticsHandle.dispose();
});
testWidgets('finds Semantically labeled widgets by RegExp', (WidgetTester tester) async {
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
await tester.pumpWidget(_boilerplate(
Semantics(
container: true,
child: Row(children: const <Widget>[
Text('Hello'),
Text('World'),
]),
),
));
expect(find.bySemanticsLabel('Hello'), findsNothing);
expect(find.bySemanticsLabel(RegExp(r'^Hello')), findsOneWidget);
semanticsHandle.dispose();
});
testWidgets('finds Semantically labeled widgets without explicit Semantics', (WidgetTester tester) async {
final SemanticsHandle semanticsHandle = tester.ensureSemantics();
await tester.pumpWidget(_boilerplate(
const SimpleCustomSemanticsWidget('Foo')
));
expect(find.bySemanticsLabel('Foo'), findsOneWidget);
semanticsHandle.dispose();
});
});
group('hitTestable', () {
testWidgets('excludes non-hit-testable widgets', (WidgetTester tester) async {
await tester.pumpWidget(
_boilerplate(IndexedStack(
sizing: StackFit.expand,
children: <Widget>[
GestureDetector(
key: const ValueKey<int>(0),
behavior: HitTestBehavior.opaque,
onTap: () { },
child: const SizedBox.expand(),
),
GestureDetector(
key: const ValueKey<int>(1),
behavior: HitTestBehavior.opaque,
onTap: () { },
child: const SizedBox.expand(),
),
],
)),
);
expect(find.byType(GestureDetector), findsNWidgets(2));
final Finder hitTestable = find.byType(GestureDetector).hitTestable();
expect(hitTestable, findsOneWidget);
expect(tester.widget(hitTestable).key, const ValueKey<int>(0));
});
});
testWidgets('ChainedFinders chain properly', (WidgetTester tester) async {
final GlobalKey key1 = GlobalKey();
await tester.pumpWidget(
_boilerplate(Column(
children: <Widget>[
Container(
key: key1,
child: const Text('1'),
),
const Text('2'),
],
)),
);
// Get the text back. By correctly chaining the descendant finder's
// candidates, it should find 1 instead of 2. If the _LastFinder wasn't
// correctly chained after the descendant's candidates, the last element
// with a Text widget would have been 2.
final Text text = find.descendant(
of: find.byKey(key1),
matching: find.byType(Text),
).last.evaluate().single.widget as Text;
expect(text.data, '1');
});
}
Widget _boilerplate(Widget child) {
return Directionality(
textDirection: TextDirection.ltr,
child: child,
);
}
class SimpleCustomSemanticsWidget extends LeafRenderObjectWidget {
const SimpleCustomSemanticsWidget(this.label, {Key? key}) : super(key: key);
final String label;
@override
RenderObject createRenderObject(BuildContext context) => SimpleCustomSemanticsRenderObject(label);
}
class SimpleCustomSemanticsRenderObject extends RenderBox {
SimpleCustomSemanticsRenderObject(this.label);
final String label;
@override
bool get sizedByParent => true;
@override
Size computeDryLayout(BoxConstraints constraints) {
return constraints.smallest;
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
config..label = label..textDirection = TextDirection.ltr;
}
}