diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index 7b49f81eb15..fd70a6b5eec 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -698,7 +698,7 @@ class TooltipState extends State with SingleTickerProviderStateMixin { _enableFeedback = widget.enableFeedback ?? tooltipTheme.enableFeedback ?? _defaultEnableFeedback; Widget result = Semantics( - label: _excludeFromSemantics + tooltip: _excludeFromSemantics ? null : _tooltipMessage, child: widget.child, diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 7d204b80767..32fb58e9433 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -3859,6 +3859,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox { AttributedString? attributedIncreasedValue, AttributedString? attributedDecreasedValue, AttributedString? attributedHint, + String? tooltip, SemanticsHintOverrides? hintOverrides, TextDirection? textDirection, SemanticsSortKey? sortKey, @@ -3917,6 +3918,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox { _attributedIncreasedValue = attributedIncreasedValue, _attributedDecreasedValue = attributedDecreasedValue, _attributedHint = attributedHint, + _tooltip = tooltip, _hintOverrides = hintOverrides, _textDirection = textDirection, _sortKey = sortKey, @@ -4311,6 +4313,18 @@ class RenderSemanticsAnnotations extends RenderProxyBox { markNeedsSemanticsUpdate(); } + /// If non-null, sets the [SemanticsNode.tooltip] semantic to the given value. + /// + /// The reading direction is given by [textDirection]. + String? get tooltip => _tooltip; + String? _tooltip; + set tooltip(String? value) { + if (_tooltip == value) + return; + _tooltip = value; + markNeedsSemanticsUpdate(); + } + /// If non-null, sets the [SemanticsConfiguration.hintOverrides] to the given value. SemanticsHintOverrides? get hintOverrides => _hintOverrides; SemanticsHintOverrides? _hintOverrides; @@ -4843,6 +4857,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox { config.attributedDecreasedValue = attributedDecreasedValue!; if (attributedHint != null) config.attributedHint = attributedHint!; + if (tooltip != null) + config.tooltip = tooltip!; if (hintOverrides != null && hintOverrides!.isNotEmpty) config.hintOverrides = hintOverrides; if (scopesRoute != null) diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index 851cd5e8819..a7373e589d1 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -316,6 +316,7 @@ class SemanticsData with Diagnosticable { required this.attributedIncreasedValue, required this.attributedDecreasedValue, required this.attributedHint, + required this.tooltip, required this.textDirection, required this.rect, required this.elevation, @@ -339,6 +340,7 @@ class SemanticsData with Diagnosticable { assert(attributedDecreasedValue != null), assert(attributedIncreasedValue != null), assert(attributedHint != null), + assert(tooltip == '' || textDirection != null, 'A SemanticsData object with tooltip "$tooltip" had a null textDirection.'), assert(attributedLabel.string == '' || textDirection != null, 'A SemanticsData object with label "${attributedLabel.string}" had a null textDirection.'), assert(attributedValue.string == '' || textDirection != null, 'A SemanticsData object with value "${attributedValue.string}" had a null textDirection.'), assert(attributedDecreasedValue.string == '' || textDirection != null, 'A SemanticsData object with decreasedValue "${attributedDecreasedValue.string}" had a null textDirection.'), @@ -429,6 +431,11 @@ class SemanticsData with Diagnosticable { /// See also [hint], which exposes just the raw text. final AttributedString attributedHint; + /// A textual description of the widget's tooltip. + /// + /// The reading direction is given by [textDirection]. + final String tooltip; + /// The reading direction for the text in [label], [value], /// [increasedValue], [decreasedValue], and [hint]. final TextDirection? textDirection; @@ -587,6 +594,7 @@ class SemanticsData with Diagnosticable { properties.add(AttributedStringProperty('increasedValue', attributedIncreasedValue)); properties.add(AttributedStringProperty('decreasedValue', attributedDecreasedValue)); properties.add(AttributedStringProperty('hint', attributedHint)); + properties.add(StringProperty('tooltip', tooltip, defaultValue: '')); properties.add(EnumProperty('textDirection', textDirection, defaultValue: null)); if (textSelection?.isValid ?? false) properties.add(MessageProperty('textSelection', '[${textSelection!.start}, ${textSelection!.end}]')); @@ -610,6 +618,7 @@ class SemanticsData with Diagnosticable { && other.attributedIncreasedValue == attributedIncreasedValue && other.attributedDecreasedValue == attributedDecreasedValue && other.attributedHint == attributedHint + && other.tooltip == tooltip && other.textDirection == textDirection && other.rect == rect && setEquals(other.tags, tags) @@ -637,6 +646,7 @@ class SemanticsData with Diagnosticable { attributedIncreasedValue, attributedDecreasedValue, attributedHint, + tooltip, textDirection, rect, tags, @@ -648,8 +658,8 @@ class SemanticsData with Diagnosticable { scrollExtentMin, platformViewId, maxValueLength, - currentValueLength, Object.hash( + currentValueLength, transform, elevation, thickness, @@ -785,6 +795,7 @@ class SemanticsProperties extends DiagnosticableTree { this.decreasedValue, this.attributedDecreasedValue, this.hint, + this.tooltip, this.attributedHint, this.hintOverrides, this.textDirection, @@ -1178,6 +1189,16 @@ class SemanticsProperties extends DiagnosticableTree { /// * [hint] for a plain string version of this property. final AttributedString? attributedHint; + /// Provides a textual description of the widget's tooltip. + /// + /// In Android, this property sets the `AccessibilityNodeInfo.setTooltipText`. + /// In iOS, this property is appended to the end of the + /// `UIAccessibilityElement.accessibilityLabel`. + /// + /// If a [tooltip] is provided, there must either by an ambient + /// [Directionality] or an explicit [textDirection] should be provided. + final String? tooltip; + /// Provides hint values which override the default hints on supported /// platforms. /// @@ -1469,6 +1490,7 @@ class SemanticsProperties extends DiagnosticableTree { properties.add(AttributedStringProperty('attributedDecreasedValue', attributedDecreasedValue, defaultValue: null)); properties.add(StringProperty('hint', hint, defaultValue: null)); properties.add(AttributedStringProperty('attributedHint', attributedHint, defaultValue: null)); + properties.add(StringProperty('tooltip', tooltip)); properties.add(EnumProperty('textDirection', textDirection, defaultValue: null)); properties.add(DiagnosticsProperty('sortKey', sortKey, defaultValue: null)); properties.add(DiagnosticsProperty('hintOverrides', hintOverrides, defaultValue: null)); @@ -1898,6 +1920,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { || _attributedValue != config.attributedValue || _attributedIncreasedValue != config.attributedIncreasedValue || _attributedDecreasedValue != config.attributedDecreasedValue + || _tooltip != config.tooltip || _flags != config._flags || _textDirection != config.textDirection || _sortKey != config._sortKey @@ -2027,6 +2050,12 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { AttributedString get attributedHint => _attributedHint; AttributedString _attributedHint = _kEmptyConfig.attributedHint; + /// A textual description of the widget's tooltip. + /// + /// The reading direction is given by [textDirection]. + String get tooltip => _tooltip; + String _tooltip = _kEmptyConfig.tooltip; + /// The elevation along the z-axis at which the [rect] of this [SemanticsNode] /// is located above its parent. /// @@ -2235,6 +2264,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { _attributedIncreasedValue = config.attributedIncreasedValue; _attributedDecreasedValue = config.attributedDecreasedValue; _attributedHint = config.attributedHint; + _tooltip = config.tooltip; _hintOverrides = config.hintOverrides; _elevation = config.elevation; _thickness = config.thickness; @@ -2282,6 +2312,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { AttributedString attributedIncreasedValue = _attributedIncreasedValue; AttributedString attributedDecreasedValue = _attributedDecreasedValue; AttributedString attributedHint = _attributedHint; + String tooltip = _tooltip; TextDirection? textDirection = _textDirection; Set? mergedTags = tags == null ? null : Set.of(tags!); TextSelection? textSelection = _textSelection; @@ -2336,6 +2367,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { attributedIncreasedValue = node._attributedIncreasedValue; if (attributedDecreasedValue == null || attributedDecreasedValue.string == '') attributedDecreasedValue = node._attributedDecreasedValue; + if (tooltip == '') + tooltip = node._tooltip; if (node.tags != null) { mergedTags ??= {}; mergedTags!.addAll(node.tags!); @@ -2385,6 +2418,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { attributedIncreasedValue: attributedIncreasedValue, attributedDecreasedValue: attributedDecreasedValue, attributedHint: attributedHint, + tooltip: tooltip, textDirection: textDirection, rect: rect, transform: transform, @@ -2457,6 +2491,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { decreasedValueAttributes: data.attributedDecreasedValue.attributes, hint: data.attributedHint.string, hintAttributes: data.attributedHint.attributes, + tooltip: data.tooltip, textDirection: data.textDirection, textSelectionBase: data.textSelection != null ? data.textSelection!.baseOffset : -1, textSelectionExtent: data.textSelection != null ? data.textSelection!.extentOffset : -1, @@ -2595,6 +2630,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { properties.add(AttributedStringProperty('increasedValue', _attributedIncreasedValue)); properties.add(AttributedStringProperty('decreasedValue', _attributedDecreasedValue)); properties.add(AttributedStringProperty('hint', _attributedHint)); + properties.add(StringProperty('tooltip', _tooltip, defaultValue: '')); properties.add(EnumProperty('textDirection', _textDirection, defaultValue: null)); properties.add(DiagnosticsProperty('sortKey', sortKey, defaultValue: null)); if (_textSelection?.isValid ?? false) @@ -3955,6 +3991,16 @@ class SemanticsConfiguration { _hasBeenAnnotated = true; } + /// A textual description of the widget's tooltip. + /// + /// The reading direction is given by [textDirection]. + String get tooltip => _tooltip; + String _tooltip = ''; + set tooltip(String tooltip) { + _tooltip = tooltip; + _hasBeenAnnotated = true; + } + /// Provides hint values which override the default hints on supported /// platforms. SemanticsHintOverrides? get hintOverrides => _hintOverrides; @@ -4420,6 +4466,8 @@ class SemanticsConfiguration { otherAttributedString: child._attributedHint, otherTextDirection: child.textDirection, ); + if (_tooltip == '') + _tooltip = child._tooltip; _thickness = math.max(_thickness, child._thickness + child._elevation); @@ -4442,6 +4490,7 @@ class SemanticsConfiguration { .._attributedDecreasedValue = _attributedDecreasedValue .._attributedHint = _attributedHint .._hintOverrides = _hintOverrides + .._tooltip = _tooltip .._elevation = _elevation .._thickness = _thickness .._flags = _flags diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 417f09f2a18..f565db3598e 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -6695,6 +6695,7 @@ class Semantics extends SingleChildRenderObjectWidget { AttributedString? attributedDecreasedValue, String? hint, AttributedString? attributedHint, + String? tooltip, String? onTapHint, String? onLongPressHint, TextDirection? textDirection, @@ -6759,6 +6760,7 @@ class Semantics extends SingleChildRenderObjectWidget { attributedDecreasedValue: attributedDecreasedValue, hint: hint, attributedHint: attributedHint, + tooltip: tooltip, textDirection: textDirection, sortKey: sortKey, tagForChildren: tagForChildren, @@ -6901,6 +6903,7 @@ class Semantics extends SingleChildRenderObjectWidget { attributedIncreasedValue: _effectiveAttributedIncreasedValue, attributedDecreasedValue: _effectiveAttributedDecreasedValue, attributedHint: _effectiveAttributedHint, + tooltip: properties.tooltip, hintOverrides: properties.hintOverrides, textDirection: _getTextDirection(context), sortKey: properties.sortKey, @@ -6936,7 +6939,8 @@ class Semantics extends SingleChildRenderObjectWidget { final bool containsText = properties.attributedLabel != null || properties.label != null || properties.value != null || - properties.hint != null; + properties.hint != null || + properties.tooltip != null; if (!containsText) return null; @@ -6977,6 +6981,7 @@ class Semantics extends SingleChildRenderObjectWidget { ..attributedIncreasedValue = _effectiveAttributedIncreasedValue ..attributedDecreasedValue = _effectiveAttributedDecreasedValue ..attributedHint = _effectiveAttributedHint + ..tooltip = properties.tooltip ..hintOverrides = properties.hintOverrides ..namesRoute = properties.namesRoute ..textDirection = _getTextDirection(context) diff --git a/packages/flutter/lib/src/widgets/semantics_debugger.dart b/packages/flutter/lib/src/widgets/semantics_debugger.dart index b7796248921..062659186cd 100644 --- a/packages/flutter/lib/src/widgets/semantics_debugger.dart +++ b/packages/flutter/lib/src/widgets/semantics_debugger.dart @@ -281,27 +281,33 @@ class _SemanticsDebuggerPainter extends CustomPainter { assert(data.attributedLabel != null); final String message; - if (data.attributedLabel.string.isEmpty) { + final String tooltipAndLabel = [ + if (data.tooltip.isNotEmpty) + data.tooltip, + if (data.attributedLabel.string.isNotEmpty) + data.attributedLabel.string, + ].join('\n'); + if (tooltipAndLabel.isEmpty) { message = annotations.join('; '); } else { - final String label; + final String effectivelabel; if (data.textDirection == null) { - label = '${Unicode.FSI}${data.attributedLabel.string}${Unicode.PDI}'; + effectivelabel = '${Unicode.FSI}$tooltipAndLabel${Unicode.PDI}'; annotations.insert(0, 'MISSING TEXT DIRECTION'); } else { switch (data.textDirection!) { case TextDirection.rtl: - label = '${Unicode.RLI}${data.attributedLabel.string}${Unicode.PDF}'; + effectivelabel = '${Unicode.RLI}$tooltipAndLabel${Unicode.PDF}'; break; case TextDirection.ltr: - label = data.attributedLabel.string; + effectivelabel = tooltipAndLabel; break; } } if (annotations.isEmpty) { - message = label; + message = effectivelabel; } else { - message = '$label (${annotations.join('; ')})'; + message = '$effectivelabel (${annotations.join('; ')})'; } } diff --git a/packages/flutter/test/material/back_button_test.dart b/packages/flutter/test/material/back_button_test.dart index eb4db41a6ba..d4ff9a92ffa 100644 --- a/packages/flutter/test/material/back_button_test.dart +++ b/packages/flutter/test/material/back_button_test.dart @@ -154,7 +154,7 @@ void main() { await tester.pumpAndSettle(); expect(tester.getSemantics(find.byType(BackButton)), matchesSemantics( - label: 'Back', + tooltip: 'Back', isButton: true, hasEnabledState: true, isEnabled: true, diff --git a/packages/flutter/test/material/calendar_date_picker_test.dart b/packages/flutter/test/material/calendar_date_picker_test.dart index bf1add0ef3b..f21300a0438 100644 --- a/packages/flutter/test/material/calendar_date_picker_test.dart +++ b/packages/flutter/test/material/calendar_date_picker_test.dart @@ -649,7 +649,7 @@ void main() { // Prev/Next month buttons. expect(tester.getSemantics(previousMonthIcon), matchesSemantics( - label: 'Previous month', + tooltip: 'Previous month', isButton: true, hasTapAction: true, isEnabled: true, @@ -657,7 +657,7 @@ void main() { isFocusable: true, )); expect(tester.getSemantics(nextMonthIcon), matchesSemantics( - label: 'Next month', + tooltip: 'Next month', isButton: true, hasTapAction: true, isEnabled: true, diff --git a/packages/flutter/test/material/chip_test.dart b/packages/flutter/test/material/chip_test.dart index 80cb5ccec09..ea4c7947a20 100644 --- a/packages/flutter/test/material/chip_test.dart +++ b/packages/flutter/test/material/chip_test.dart @@ -1964,7 +1964,7 @@ void main() { ], children: [ TestSemantics( - label: 'Delete', + tooltip: 'Delete', actions: [SemanticsAction.tap], textDirection: TextDirection.ltr, flags: [ diff --git a/packages/flutter/test/material/date_picker_test.dart b/packages/flutter/test/material/date_picker_test.dart index 45ab8b05ccb..525148fb82c 100644 --- a/packages/flutter/test/material/date_picker_test.dart +++ b/packages/flutter/test/material/date_picker_test.dart @@ -817,7 +817,7 @@ void main() { // Input mode toggle button expect(tester.getSemantics(switchToInputIcon), matchesSemantics( - label: 'Switch to input', + tooltip: 'Switch to input', isButton: true, hasTapAction: true, isEnabled: true, @@ -860,7 +860,7 @@ void main() { // Input mode toggle button expect(tester.getSemantics(switchToCalendarIcon), matchesSemantics( - label: 'Switch to calendar', + tooltip: 'Switch to calendar', isButton: true, hasTapAction: true, isEnabled: true, diff --git a/packages/flutter/test/material/floating_action_button_test.dart b/packages/flutter/test/material/floating_action_button_test.dart index 3b41bc336d0..987e8a99050 100644 --- a/packages/flutter/test/material/floating_action_button_test.dart +++ b/packages/flutter/test/material/floating_action_button_test.dart @@ -670,7 +670,7 @@ void main() { semantics.dispose(); }); - testWidgets('Tooltip is used as semantics label', (WidgetTester tester) async { + testWidgets('Tooltip is used as semantics tooltip', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( @@ -697,7 +697,7 @@ void main() { ], children: [ TestSemantics( - label: 'Add Photo', + tooltip: 'Add Photo', actions: [ SemanticsAction.tap, ], diff --git a/packages/flutter/test/material/search_test.dart b/packages/flutter/test/material/search_test.dart index 9d7ccb30dac..9a6c24e1529 100644 --- a/packages/flutter/test/material/search_test.dart +++ b/packages/flutter/test/material/search_test.dart @@ -616,7 +616,7 @@ void main() { SemanticsFlag.isFocusable, ], actions: [SemanticsAction.tap], - label: 'Back', + tooltip: 'Back', textDirection: TextDirection.ltr, ), TestSemantics( diff --git a/packages/flutter/test/material/tooltip_test.dart b/packages/flutter/test/material/tooltip_test.dart index 842cf8b0c11..2d25385bc6d 100644 --- a/packages/flutter/test/material/tooltip_test.dart +++ b/packages/flutter/test/material/tooltip_test.dart @@ -1263,7 +1263,7 @@ void main() { children: [ TestSemantics.rootChild( id: 1, - label: 'TIP', + tooltip: 'TIP', textDirection: TextDirection.ltr, ), ], @@ -1462,7 +1462,8 @@ void main() { flags: [SemanticsFlag.scopesRoute], children: [ TestSemantics( - label: 'Foo\nBar', + tooltip: 'Foo', + label: 'Bar', textDirection: TextDirection.ltr, ), ], diff --git a/packages/flutter/test/material/tooltip_theme_test.dart b/packages/flutter/test/material/tooltip_theme_test.dart index 999c335115b..2fedbf3a5e6 100644 --- a/packages/flutter/test/material/tooltip_theme_test.dart +++ b/packages/flutter/test/material/tooltip_theme_test.dart @@ -1066,7 +1066,8 @@ void main() { flags: [SemanticsFlag.scopesRoute], children: [ TestSemantics( - label: 'Foo\nBar', + tooltip: 'Foo', + label: 'Bar', textDirection: TextDirection.ltr, ), ], @@ -1108,7 +1109,8 @@ void main() { flags: [SemanticsFlag.scopesRoute], children: [ TestSemantics( - label: 'Foo\nBar', + tooltip: 'Foo', + label: 'Bar', textDirection: TextDirection.ltr, ), ], diff --git a/packages/flutter/test/semantics/semantics_test.dart b/packages/flutter/test/semantics/semantics_test.dart index ebc240b3436..4f2be0e967e 100644 --- a/packages/flutter/test/semantics/semantics_test.dart +++ b/packages/flutter/test/semantics/semantics_test.dart @@ -561,6 +561,7 @@ void main() { ' increasedValue: ""\n' ' decreasedValue: ""\n' ' hint: ""\n' + ' tooltip: ""\n' ' textDirection: null\n' ' sortKey: null\n' ' platformViewId: null\n' @@ -659,6 +660,7 @@ void main() { ' increasedValue: ""\n' ' decreasedValue: ""\n' ' hint: ""\n' + ' tooltip: ""\n' ' textDirection: null\n' ' sortKey: null\n' ' platformViewId: null\n' diff --git a/packages/flutter/test/semantics/semantics_update_test.dart b/packages/flutter/test/semantics/semantics_update_test.dart index 78b0926dee0..0026e506743 100644 --- a/packages/flutter/test/semantics/semantics_update_test.dart +++ b/packages/flutter/test/semantics/semantics_update_test.dart @@ -158,7 +158,8 @@ void main() { 'properties: SemanticsProperties, ' 'attributedLabel: "label" [SpellOutStringAttribute(TextRange(start: 0, end: 5))], ' 'attributedValue: "value" [LocaleStringAttribute(TextRange(start: 0, end: 5), en-MX)], ' - 'attributedHint: "hint" [SpellOutStringAttribute(TextRange(start: 1, end: 2))]' // ignore: missing_whitespace_between_adjacent_strings + 'attributedHint: "hint" [SpellOutStringAttribute(TextRange(start: 1, end: 2))], ' + 'tooltip: null'// ignore: missing_whitespace_between_adjacent_strings ')', ); diff --git a/packages/flutter/test/widgets/semantics_test.dart b/packages/flutter/test/widgets/semantics_test.dart index d8e6fc636d9..5586aa83860 100644 --- a/packages/flutter/test/widgets/semantics_test.dart +++ b/packages/flutter/test/widgets/semantics_test.dart @@ -61,6 +61,34 @@ void main() { semantics.dispose(); }, semanticsEnabled: false); + testWidgets('Semantics tooltip', (WidgetTester tester) async { + final SemanticsTester semantics = SemanticsTester(tester); + + final TestSemantics expectedSemantics = TestSemantics.root( + children: [ + TestSemantics.rootChild( + tooltip: 'test1', + textDirection: TextDirection.ltr, + ), + ], + ); + + await tester.pumpWidget( + Semantics( + tooltip: 'test1', + textDirection: TextDirection.ltr, + ), + ); + + expect(semantics, hasSemantics( + expectedSemantics, + ignoreTransform: true, + ignoreRect: true, + ignoreId: true, + )); + semantics.dispose(); + }); + testWidgets('Detach and reattach assert', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); final GlobalKey key = GlobalKey(); diff --git a/packages/flutter/test/widgets/semantics_tester.dart b/packages/flutter/test/widgets/semantics_tester.dart index 3c4cc90ac81..e15332dfc7d 100644 --- a/packages/flutter/test/widgets/semantics_tester.dart +++ b/packages/flutter/test/widgets/semantics_tester.dart @@ -39,6 +39,7 @@ class TestSemantics { this.actions = 0, this.label = '', this.value = '', + this.tooltip = '', this.increasedValue = '', this.decreasedValue = '', this.hint = '', @@ -72,6 +73,7 @@ class TestSemantics { this.increasedValue = '', this.decreasedValue = '', this.hint = '', + this.tooltip = '', this.textDirection, this.transform, this.textSelection, @@ -110,6 +112,7 @@ class TestSemantics { this.label = '', this.hint = '', this.value = '', + this.tooltip = '', this.increasedValue = '', this.decreasedValue = '', this.textDirection, @@ -176,6 +179,9 @@ class TestSemantics { /// performed on this node. final String hint; + /// A textual tooltip of this node. + final String tooltip; + /// The reading direction of the [label]. /// /// Even if this is not set, the [hasSemantics] matcher will verify that if a @@ -292,6 +298,8 @@ class TestSemantics { return fail('expected node id $id to have decreasedValue "$decreasedValue" but found value "${nodeData.decreasedValue}".'); if (hint != nodeData.hint) return fail('expected node id $id to have hint "$hint" but found hint "${nodeData.hint}".'); + if (tooltip != nodeData.tooltip) + return fail('expected node id $id to have tooltip "$tooltip" but found hint "${nodeData.tooltip}".'); if (textDirection != null && textDirection != nodeData.textDirection) return fail('expected node id $id to have textDirection "$textDirection" but found "${nodeData.textDirection}".'); if ((nodeData.label != '' || nodeData.value != '' || nodeData.hint != '' || node.increasedValue != '' || node.decreasedValue != '') && nodeData.textDirection == null) @@ -365,6 +373,8 @@ class TestSemantics { buf.writeln("$indent decreasedValue: '$decreasedValue',"); if (hint != null && hint != '') buf.writeln("$indent hint: '$hint',"); + if (tooltip != null && tooltip != '') + buf.writeln("$indent tooltip: '$tooltip',"); if (textDirection != null) buf.writeln('$indent textDirection: $textDirection,'); if (textSelection?.isValid ?? false) diff --git a/packages/flutter_test/lib/src/accessibility.dart b/packages/flutter_test/lib/src/accessibility.dart index 5bb9262a917..f23c589b81d 100644 --- a/packages/flutter_test/lib/src/accessibility.dart +++ b/packages/flutter_test/lib/src/accessibility.dart @@ -228,7 +228,7 @@ class LabeledTapTargetGuideline extends AccessibilityGuideline { !data.hasAction(ui.SemanticsAction.tap)) { return result; } - if (data.label == null || data.label.isEmpty) { + if ((data.label == null || data.label.isEmpty) && (data.tooltip == null || data.tooltip.isEmpty)) { result += Evaluation.fail( '$node: expected tappable node to have semantic label, ' 'but none was found.\n', diff --git a/packages/flutter_test/lib/src/matchers.dart b/packages/flutter_test/lib/src/matchers.dart index bad708fe4f7..338e5bcaf18 100644 --- a/packages/flutter_test/lib/src/matchers.dart +++ b/packages/flutter_test/lib/src/matchers.dart @@ -494,6 +494,7 @@ Matcher matchesSemantics({ String? increasedValue, AttributedString? attributedIncreasedValue, String? decreasedValue, + String? tooltip, AttributedString? attributedDecreasedValue, TextDirection? textDirection, Rect? rect, @@ -625,6 +626,7 @@ Matcher matchesSemantics({ value: value, attributedValue: attributedValue, increasedValue: increasedValue, + tooltip: tooltip, attributedIncreasedValue: attributedIncreasedValue, decreasedValue: decreasedValue, attributedDecreasedValue: attributedDecreasedValue, @@ -1783,6 +1785,7 @@ class _MatchesSemanticsData extends Matcher { this.attributedIncreasedValue, this.decreasedValue, this.attributedDecreasedValue, + this.tooltip, this.flags, this.actions, this.textDirection, @@ -1808,6 +1811,7 @@ class _MatchesSemanticsData extends Matcher { final AttributedString? attributedIncreasedValue; final String? decreasedValue; final AttributedString? attributedDecreasedValue; + final String? tooltip; final SemanticsHintOverrides? hintOverrides; final List? actions; final List? customActions; @@ -1845,6 +1849,8 @@ class _MatchesSemanticsData extends Matcher { description.add(' with decreasedValue: $decreasedValue '); if (attributedDecreasedValue != null) description.add(' with attributedDecreasedValue: $attributedDecreasedValue'); + if (tooltip != null) + description.add(' with tooltip: $tooltip'); if (actions != null) description.add(' with actions: ').addDescriptionOf(actions); if (flags != null) @@ -1942,6 +1948,8 @@ class _MatchesSemanticsData extends Matcher { return failWithDescription( matchState, 'attributedDecreasedValue was: ${data.attributedDecreasedValue}'); } + if (tooltip != null && tooltip != data.tooltip) + return failWithDescription(matchState, 'tooltip was: ${data.tooltip}'); if (textDirection != null && textDirection != data.textDirection) return failWithDescription(matchState, 'textDirection was: $textDirection'); if (rect != null && rect != data.rect) diff --git a/packages/flutter_test/test/matchers_test.dart b/packages/flutter_test/test/matchers_test.dart index 48a9d781035..4632aba6fc4 100644 --- a/packages/flutter_test/test/matchers_test.dart +++ b/packages/flutter_test/test/matchers_test.dart @@ -566,6 +566,7 @@ void main() { attributedValue: AttributedString('c'), attributedDecreasedValue: AttributedString('d'), attributedHint: AttributedString('e'), + tooltip: 'f', textDirection: TextDirection.ltr, rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0), elevation: 3.0,