mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add RichText support to find.text() (#87197)
This commit is contained in:
parent
db3cc6e318
commit
87b17c8ccf
@ -23,8 +23,20 @@ const CommonFinders find = CommonFinders._();
|
||||
class CommonFinders {
|
||||
const CommonFinders._();
|
||||
|
||||
/// Finds [Text] and [EditableText] widgets containing string equal to the
|
||||
/// `text` argument.
|
||||
/// Finds [Text], [EditableText], and optionally [RichText] widgets
|
||||
/// containing string equal to the `text` argument.
|
||||
///
|
||||
/// If `findRichText` is false, all standalone [RichText] widgets are
|
||||
/// ignored and `text` is matched with [Text.data] or [Text.textSpan].
|
||||
/// If `findRichText` is true, [RichText] widgets (and therefore also
|
||||
/// [Text] and [Text.rich] widgets) are matched by comparing the
|
||||
/// [InlineSpan.toPlainText] with the given `text`.
|
||||
///
|
||||
/// For [EditableText] widgets, the `text` is always compared to the current
|
||||
/// value of the [EditableText.controller].
|
||||
///
|
||||
/// If the `skipOffstage` argument is true (the default), then this skips
|
||||
/// nodes that are [Offstage] or that are from inactive [Route]s.
|
||||
///
|
||||
/// ## Sample code
|
||||
///
|
||||
@ -32,9 +44,26 @@ class CommonFinders {
|
||||
/// 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.
|
||||
Finder text(String text, { bool skipOffstage = true }) => _TextFinder(text, skipOffstage: skipOffstage);
|
||||
/// This will match [Text], [Text.rich], and [EditableText] widgets that
|
||||
/// contain the "Back" string.
|
||||
///
|
||||
/// ```dart
|
||||
/// expect(find.text('Close', findRichText: true), findsOneWidget);
|
||||
/// ```
|
||||
///
|
||||
/// This will match [Text], [Text.rich], [EditableText], as well as standalone
|
||||
/// [RichText] widgets that contain the "Close" string.
|
||||
Finder text(
|
||||
String text, {
|
||||
bool findRichText = false,
|
||||
bool skipOffstage = true,
|
||||
}) {
|
||||
return _TextFinder(
|
||||
text,
|
||||
findRichText: findRichText,
|
||||
skipOffstage: skipOffstage,
|
||||
);
|
||||
}
|
||||
|
||||
/// Finds [Text] and [EditableText] widgets which contain the given
|
||||
/// `pattern` argument.
|
||||
@ -548,26 +577,65 @@ abstract class MatchFinder extends Finder {
|
||||
}
|
||||
|
||||
class _TextFinder extends MatchFinder {
|
||||
_TextFinder(this.text, { bool skipOffstage = true }) : super(skipOffstage: skipOffstage);
|
||||
_TextFinder(
|
||||
this.text, {
|
||||
this.findRichText = false,
|
||||
bool skipOffstage = true,
|
||||
}) : super(skipOffstage: skipOffstage);
|
||||
|
||||
final String text;
|
||||
|
||||
/// Whether standalone [RichText] widgets should be found or not.
|
||||
///
|
||||
/// Defaults to `false`.
|
||||
///
|
||||
/// If disabled, only [Text] widgets will be matched. [RichText] widgets
|
||||
/// *without* a [Text] ancestor will be ignored.
|
||||
/// If enabled, only [RichText] widgets will be matched. This *implicitly*
|
||||
/// matches [Text] widgets as well since they always insert a [RichText]
|
||||
/// child.
|
||||
///
|
||||
/// In either case, [EditableText] widgets will also be matched.
|
||||
final bool findRichText;
|
||||
|
||||
@override
|
||||
String get description => 'text "$text"';
|
||||
|
||||
@override
|
||||
bool matches(Element candidate) {
|
||||
final Widget widget = candidate.widget;
|
||||
if (widget is EditableText)
|
||||
return _matchesEditableText(widget);
|
||||
|
||||
if (!findRichText)
|
||||
return _matchesNonRichText(widget);
|
||||
// It would be sufficient to always use _matchesRichText if we wanted to
|
||||
// match both standalone RichText widgets as well as Text widgets. However,
|
||||
// the find.text() finder used to always ignore standalone RichText widgets,
|
||||
// which is why we need the _matchesNonRichText method in order to not be
|
||||
// backwards-compatible and not break existing tests.
|
||||
return _matchesRichText(widget);
|
||||
}
|
||||
|
||||
bool _matchesRichText(Widget widget) {
|
||||
if (widget is RichText)
|
||||
return widget.text.toPlainText() == text;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _matchesNonRichText(Widget widget) {
|
||||
if (widget is Text) {
|
||||
if (widget.data != null)
|
||||
return widget.data == text;
|
||||
assert(widget.textSpan != null);
|
||||
return widget.textSpan!.toPlainText() == text;
|
||||
} else if (widget is EditableText) {
|
||||
return widget.controller.text == text;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _matchesEditableText(EditableText widget) {
|
||||
return widget.controller.text == text;
|
||||
}
|
||||
}
|
||||
|
||||
class _TextContainingFinder extends MatchFinder {
|
||||
|
@ -27,6 +27,84 @@ void main() {
|
||||
|
||||
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', findRichText: false), 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', findRichText: false), findsOneWidget);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('textContaining', () {
|
||||
|
Loading…
Reference in New Issue
Block a user