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 {
|
class CommonFinders {
|
||||||
const CommonFinders._();
|
const CommonFinders._();
|
||||||
|
|
||||||
/// Finds [Text] and [EditableText] widgets containing string equal to the
|
/// Finds [Text], [EditableText], and optionally [RichText] widgets
|
||||||
/// `text` argument.
|
/// 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
|
/// ## Sample code
|
||||||
///
|
///
|
||||||
@ -32,9 +44,26 @@ class CommonFinders {
|
|||||||
/// expect(find.text('Back'), findsOneWidget);
|
/// expect(find.text('Back'), findsOneWidget);
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// If the `skipOffstage` argument is true (the default), then this skips
|
/// This will match [Text], [Text.rich], and [EditableText] widgets that
|
||||||
/// nodes that are [Offstage] or that are from inactive [Route]s.
|
/// contain the "Back" string.
|
||||||
Finder text(String text, { bool skipOffstage = true }) => _TextFinder(text, skipOffstage: skipOffstage);
|
///
|
||||||
|
/// ```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
|
/// Finds [Text] and [EditableText] widgets which contain the given
|
||||||
/// `pattern` argument.
|
/// `pattern` argument.
|
||||||
@ -548,26 +577,65 @@ abstract class MatchFinder extends Finder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _TextFinder extends MatchFinder {
|
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;
|
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
|
@override
|
||||||
String get description => 'text "$text"';
|
String get description => 'text "$text"';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matches(Element candidate) {
|
bool matches(Element candidate) {
|
||||||
final Widget widget = candidate.widget;
|
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 is Text) {
|
||||||
if (widget.data != null)
|
if (widget.data != null)
|
||||||
return widget.data == text;
|
return widget.data == text;
|
||||||
assert(widget.textSpan != null);
|
assert(widget.textSpan != null);
|
||||||
return widget.textSpan!.toPlainText() == text;
|
return widget.textSpan!.toPlainText() == text;
|
||||||
} else if (widget is EditableText) {
|
|
||||||
return widget.controller.text == text;
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _matchesEditableText(EditableText widget) {
|
||||||
|
return widget.controller.text == text;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TextContainingFinder extends MatchFinder {
|
class _TextContainingFinder extends MatchFinder {
|
||||||
|
@ -27,6 +27,84 @@ void main() {
|
|||||||
|
|
||||||
expect(find.text('test'), findsOneWidget);
|
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', () {
|
group('textContaining', () {
|
||||||
|
Loading…
Reference in New Issue
Block a user