From 23f7985e5094c4b77aedecfc3b59bbd00c005446 Mon Sep 17 00:00:00 2001 From: Ian Hickson Date: Fri, 29 Oct 2021 11:43:05 -0700 Subject: [PATCH] Improve how AttributedStrings are presented in the widget inspector (#92450) --- .../lib/src/foundation/diagnostics.dart | 20 +- .../flutter/lib/src/semantics/semantics.dart | 336 ++++++++++++------ .../test/semantics/semantics_test.dart | 37 ++ .../test/semantics/semantics_update_test.dart | 11 + 4 files changed, 290 insertions(+), 114 deletions(-) diff --git a/packages/flutter/lib/src/foundation/diagnostics.dart b/packages/flutter/lib/src/foundation/diagnostics.dart index 0015b7351c..c6b1e0c6a1 100644 --- a/packages/flutter/lib/src/foundation/diagnostics.dart +++ b/packages/flutter/lib/src/foundation/diagnostics.dart @@ -2839,14 +2839,23 @@ class DiagnosticsProperty extends DiagnosticsNode { } } - /// If the [value] of the property equals [defaultValue] the priority [level] - /// of the property is downgraded to [DiagnosticLevel.fine] as the property - /// value is uninteresting. + /// The default value of this property, when it has not been set to a specific + /// value. + /// + /// For most [DiagnosticsProperty] classes, if the [value] of the property + /// equals [defaultValue], then the priority [level] of the property is + /// downgraded to [DiagnosticLevel.fine] on the basis that the property value + /// is uninteresting. This is implemented by [isInteresting]. /// /// The [defaultValue] is [kNoDefaultValue] by default. Otherwise it must be of /// type `T?`. final Object? defaultValue; + /// Whether to consider the property's value interesting. When a property is + /// uninteresting, its [level] is downgraded to [DiagnosticLevel.fine] + /// regardless of the value provided as the constructor's `level` argument. + bool get isInteresting => defaultValue == kNoDefaultValue || value != defaultValue; + final DiagnosticLevel _defaultLevel; /// Priority level of the diagnostic used to control which diagnostics should @@ -2870,8 +2879,7 @@ class DiagnosticsProperty extends DiagnosticsNode { if (value == null && missingIfNull) return DiagnosticLevel.warning; - // Use a low level when the value matches the default value. - if (defaultValue != kNoDefaultValue && value == defaultValue) + if (!isInteresting) return DiagnosticLevel.fine; return _defaultLevel; @@ -3549,7 +3557,9 @@ class DiagnosticsBlock extends DiagnosticsNode { @override final DiagnosticLevel level; + final String? _description; + @override final Object? value; diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart index bd391ce549..9e273218b7 100644 --- a/packages/flutter/lib/src/semantics/semantics.dart +++ b/packages/flutter/lib/src/semantics/semantics.dart @@ -255,6 +255,57 @@ class AttributedString { } } +/// A [DiagnosticsProperty] for [AttributedString]s, which shows a string +/// when there are no attributes, and more details otherwise. +class AttributedStringProperty extends DiagnosticsProperty { + /// Create a diagnostics property for an [AttributedString] object. + /// + /// Such properties are used with [SemanticsData] objects. + AttributedStringProperty( + String name, + AttributedString? value, { + bool showName = true, + this.showWhenEmpty = false, + Object? defaultValue = kNoDefaultValue, + DiagnosticLevel level = DiagnosticLevel.info, + String? description, + }) : assert(showName != null), + assert(level != null), + super( + name, + value, + showName: showName, + defaultValue: defaultValue, + level: level, + description: description, + ); + + /// Whether to show the property when the [value] is an [AttributedString] + /// whose [AttributedString.string] is the empty string. + /// + /// This overrides [defaultValue]. + final bool showWhenEmpty; + + @override + bool get isInteresting => super.isInteresting && (showWhenEmpty || (value != null && value!.string.isNotEmpty)); + + @override + String valueToString({TextTreeConfiguration? parentConfiguration}) { + if (value == null) + return 'null'; + String text = value!.string; + if (parentConfiguration != null && + !parentConfiguration.lineBreakProperties) { + // This follows a similar pattern to StringProperty. + text = text.replaceAll('\n', r'\n'); + } + if (value!.attributes.isEmpty) { + return '"$text"'; + } + return '"$text" ${value!.attributes}'; // the attributes will be in square brackets since they're a list + } +} + /// Summary information about a [SemanticsNode] object. /// /// A semantics node might [SemanticsNode.mergeAllDescendantsIntoThisNode], @@ -303,9 +354,9 @@ class SemanticsData with Diagnosticable { assert(attributedHint != null), 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(attributedHint.string == '' || textDirection != null, 'A SemanticsData object with hint "${attributedHint.string}" had a null textDirection.'), assert(attributedDecreasedValue.string == '' || textDirection != null, 'A SemanticsData object with decreasedValue "${attributedDecreasedValue.string}" had a null textDirection.'), assert(attributedIncreasedValue.string == '' || textDirection != null, 'A SemanticsData object with increasedValue "${attributedIncreasedValue.string}" had a null textDirection.'), + assert(attributedHint.string == '' || textDirection != null, 'A SemanticsData object with hint "${attributedHint.string}" had a null textDirection.'), assert(rect != null); /// A bit field of [SemanticsFlag]s that apply to this node. @@ -317,62 +368,82 @@ class SemanticsData with Diagnosticable { /// A textual description for the current label of the node. /// /// The reading direction is given by [textDirection]. + /// + /// This exposes the raw text of the [attributedLabel]. String get label => attributedLabel.string; /// A textual description for the current label of the node in /// [AttributedString] format. /// /// The reading direction is given by [textDirection]. + /// + /// See also [label], which exposes just the raw text. final AttributedString attributedLabel; /// A textual description for the current value of the node. /// /// The reading direction is given by [textDirection]. + /// + /// This exposes the raw text of the [attributedValue]. String get value => attributedValue.string; /// A textual description for the current value of the node in /// [AttributedString] format. /// /// The reading direction is given by [textDirection]. + /// + /// See also [value], which exposes just the raw text. final AttributedString attributedValue; /// The value that [value] will become after performing a /// [SemanticsAction.increase] action. /// /// The reading direction is given by [textDirection]. + /// + /// This exposes the raw text of the [attributedIncreasedValue]. String get increasedValue => attributedIncreasedValue.string; /// The value that [value] will become after performing a /// [SemanticsAction.increase] action in [AttributedString] format. /// /// The reading direction is given by [textDirection]. + /// + /// See also [increasedValue], which exposes just the raw text. final AttributedString attributedIncreasedValue; /// The value that [value] will become after performing a /// [SemanticsAction.decrease] action. /// /// The reading direction is given by [textDirection]. + /// + /// This exposes the raw text of the [attributedDecreasedValue]. String get decreasedValue => attributedDecreasedValue.string; /// The value that [value] will become after performing a /// [SemanticsAction.decrease] action in [AttributedString] format. /// /// The reading direction is given by [textDirection]. + /// + /// See also [decreasedValue], which exposes just the raw text. final AttributedString attributedDecreasedValue; /// A brief description of the result of performing an action on this node. /// /// The reading direction is given by [textDirection]. + /// + /// This exposes the raw text of the [attributedHint]. String get hint => attributedHint.string; /// A brief description of the result of performing an action on this node /// in [AttributedString] format. /// /// The reading direction is given by [textDirection]. + /// + /// See also [hint], which exposes just the raw text. final AttributedString attributedHint; - /// The reading direction for the text in [label], [value], [hint], - /// [increasedValue], and [decreasedValue]. + /// The reading direction for the text in [label], [value], + /// [increasedValue], [decreasedValue], and [hint]. final TextDirection? textDirection; /// The currently selected text (or the position of the cursor) within [value] @@ -524,11 +595,11 @@ class SemanticsData with Diagnosticable { describeEnum(flag), ]; properties.add(IterableProperty('flags', flagSummary, ifEmpty: null)); - properties.add(StringProperty('label', attributedLabel.attributes.isEmpty ? label : attributedLabel.toString(), defaultValue: '')); - properties.add(StringProperty('value', attributedValue.attributes.isEmpty? value :attributedValue.toString(), defaultValue: '')); - properties.add(StringProperty('increasedValue', attributedIncreasedValue.attributes.isEmpty? increasedValue :attributedIncreasedValue.toString(), defaultValue: '')); - properties.add(StringProperty('decreasedValue', attributedDecreasedValue.attributes.isEmpty? decreasedValue :attributedDecreasedValue.toString(), defaultValue: '')); - properties.add(StringProperty('hint', attributedHint.attributes.isEmpty? hint :attributedHint.toString(), defaultValue: '')); + properties.add(AttributedStringProperty('label', attributedLabel)); + properties.add(AttributedStringProperty('value', attributedValue)); + properties.add(AttributedStringProperty('increasedValue', attributedIncreasedValue)); + properties.add(AttributedStringProperty('decreasedValue', attributedDecreasedValue)); + properties.add(AttributedStringProperty('hint', attributedHint)); properties.add(EnumProperty('textDirection', textDirection, defaultValue: null)); if (textSelection?.isValid == true) properties.add(MessageProperty('textSelection', '[${textSelection!.start}, ${textSelection!.end}]')); @@ -977,10 +1048,10 @@ class SemanticsProperties extends DiagnosticableTree { /// /// * [SemanticsConfiguration.label] for a description of how this is exposed /// in TalkBack and VoiceOver. - /// * [attributedLabel] for a [AttributedString] version of this property. + /// * [attributedLabel] for an [AttributedString] version of this property. final String? label; - /// Provides a [AttributedString] version of textual description of the widget. + /// Provides an [AttributedString] version of textual description of the widget. /// /// If a [attributedLabel] is provided, there must either by an ambient /// [Directionality] or an explicit [textDirection] should be provided. @@ -1007,10 +1078,10 @@ class SemanticsProperties extends DiagnosticableTree { /// /// * [SemanticsConfiguration.value] for a description of how this is exposed /// in TalkBack and VoiceOver. - /// * [attributedLabel] for a [AttributedString] version of this property. + /// * [attributedLabel] for an [AttributedString] version of this property. final String? value; - /// Provides a [AttributedString] version of textual description of the value + /// Provides an [AttributedString] version of textual description of the value /// of the widget. /// /// If a [attributedValue] is provided, there must either by an ambient @@ -1040,7 +1111,7 @@ class SemanticsProperties extends DiagnosticableTree { /// /// * [SemanticsConfiguration.increasedValue] for a description of how this /// is exposed in TalkBack and VoiceOver. - /// * [attributedIncreasedValue] for a [AttributedString] version of this + /// * [attributedIncreasedValue] for an [AttributedString] version of this /// property. final String? increasedValue; @@ -1075,7 +1146,7 @@ class SemanticsProperties extends DiagnosticableTree { /// /// * [SemanticsConfiguration.decreasedValue] for a description of how this /// is exposed in TalkBack and VoiceOver. - /// * [attributedDecreasedValue] for a [AttributedString] version of this + /// * [attributedDecreasedValue] for an [AttributedString] version of this /// property. final String? decreasedValue; @@ -1109,10 +1180,10 @@ class SemanticsProperties extends DiagnosticableTree { /// /// * [SemanticsConfiguration.hint] for a description of how this is exposed /// in TalkBack and VoiceOver. - /// * [attributedHint] for a [AttributedString] version of this property. + /// * [attributedHint] for an [AttributedString] version of this property. final String? hint; - /// Provides a [AttributedString] version of brief textual description of the + /// Provides an [AttributedString] version of brief textual description of the /// result of an action performed on the widget. /// /// If a [attributedHint] is provided, there must either by an ambient @@ -1138,8 +1209,8 @@ class SemanticsProperties extends DiagnosticableTree { /// On iOS, these are always ignored and the default [hint] is used instead. final SemanticsHintOverrides? hintOverrides; - /// The reading direction of the [label], [value], [hint], [increasedValue], - /// and [decreasedValue]. + /// The reading direction of the [label], [value], [increasedValue], + /// [decreasedValue], and [hint]. /// /// Defaults to the ambient [Directionality]. final TextDirection? textDirection; @@ -1409,15 +1480,19 @@ class SemanticsProperties extends DiagnosticableTree { super.debugFillProperties(properties); properties.add(DiagnosticsProperty('checked', checked, defaultValue: null)); properties.add(DiagnosticsProperty('selected', selected, defaultValue: null)); - properties.add(StringProperty('label', label, defaultValue: '')); - properties.add(StringProperty('attributedLabel', attributedLabel.toString(), defaultValue: '')); - properties.add(StringProperty('value', value)); - properties.add(StringProperty('attributedValue', attributedValue.toString(), defaultValue: '')); - properties.add(StringProperty('hint', hint)); - properties.add(StringProperty('attributedHint', attributedHint.toString(), defaultValue: '')); + properties.add(StringProperty('label', label, defaultValue: null)); + properties.add(AttributedStringProperty('attributedLabel', attributedLabel, defaultValue: null)); + properties.add(StringProperty('value', value, defaultValue: null)); + properties.add(AttributedStringProperty('attributedValue', attributedValue, defaultValue: null)); + properties.add(StringProperty('increasedValue', value, defaultValue: null)); + properties.add(AttributedStringProperty('attributedIncreasedValue', attributedIncreasedValue, defaultValue: null)); + properties.add(StringProperty('decreasedValue', value, defaultValue: null)); + properties.add(AttributedStringProperty('attributedDecreasedValue', attributedDecreasedValue, defaultValue: null)); + properties.add(StringProperty('hint', hint, defaultValue: null)); + properties.add(AttributedStringProperty('attributedHint', attributedHint, defaultValue: null)); properties.add(EnumProperty('textDirection', textDirection, defaultValue: null)); properties.add(DiagnosticsProperty('sortKey', sortKey, defaultValue: null)); - properties.add(DiagnosticsProperty('hintOverrides', hintOverrides)); + properties.add(DiagnosticsProperty('hintOverrides', hintOverrides, defaultValue: null)); } @override @@ -1837,26 +1912,26 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { } bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) { - return _attributedLabel != config.attributedLabel || - _attributedHint != config.attributedHint || - _elevation != config.elevation || - _thickness != config.thickness || - _attributedValue != config.attributedValue || - _attributedIncreasedValue != config.attributedIncreasedValue || - _attributedDecreasedValue != config.attributedDecreasedValue || - _flags != config._flags || - _textDirection != config.textDirection || - _sortKey != config._sortKey || - _textSelection != config._textSelection || - _scrollPosition != config._scrollPosition || - _scrollExtentMax != config._scrollExtentMax || - _scrollExtentMin != config._scrollExtentMin || - _actionsAsBits != config._actionsAsBits || - indexInParent != config.indexInParent || - platformViewId != config.platformViewId || - _maxValueLength != config._maxValueLength || - _currentValueLength != config._currentValueLength || - _mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants; + return _attributedLabel != config.attributedLabel + || _attributedHint != config.attributedHint + || _elevation != config.elevation + || _thickness != config.thickness + || _attributedValue != config.attributedValue + || _attributedIncreasedValue != config.attributedIncreasedValue + || _attributedDecreasedValue != config.attributedDecreasedValue + || _flags != config._flags + || _textDirection != config.textDirection + || _sortKey != config._sortKey + || _textSelection != config._textSelection + || _scrollPosition != config._scrollPosition + || _scrollExtentMax != config._scrollExtentMax + || _scrollExtentMin != config._scrollExtentMin + || _actionsAsBits != config._actionsAsBits + || indexInParent != config.indexInParent + || platformViewId != config.platformViewId + || _maxValueLength != config._maxValueLength + || _currentValueLength != config._currentValueLength + || _mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants; } // TAGS, LABELS, ACTIONS @@ -1883,45 +1958,34 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { /// A textual description of this node. /// /// The reading direction is given by [textDirection]. + /// + /// This exposes the raw text of the [attributedLabel]. String get label => _attributedLabel.string; /// A textual description of this node in [AttributedString] format. /// /// The reading direction is given by [textDirection]. + /// + /// See also [label], which exposes just the raw text. AttributedString get attributedLabel => _attributedLabel; AttributedString _attributedLabel = _kEmptyConfig.attributedLabel; /// A textual description for the current value of the node. /// /// The reading direction is given by [textDirection]. + /// + /// This exposes the raw text of the [attributedValue]. String get value => _attributedValue.string; /// A textual description for the current value of the node in /// [AttributedString] format. /// /// The reading direction is given by [textDirection]. + /// + /// See also [value], which exposes just the raw text. AttributedString get attributedValue => _attributedValue; AttributedString _attributedValue = _kEmptyConfig.attributedValue; - /// The value that [value] will have after a [SemanticsAction.decrease] action - /// has been performed. - /// - /// This property is only valid if the [SemanticsAction.decrease] action is - /// available on this node. - /// - /// The reading direction is given by [textDirection]. - String get decreasedValue => _attributedDecreasedValue.string; - - /// The value in [AttributedString] format that [value] or [attributedValue] - /// will have after a [SemanticsAction.decrease] action has been performed. - /// - /// This property is only valid if the [SemanticsAction.decrease] action is - /// available on this node. - /// - /// The reading direction is given by [textDirection]. - AttributedString get attributedDecreasedValue => _attributedDecreasedValue; - AttributedString _attributedDecreasedValue = _kEmptyConfig.attributedDecreasedValue; - /// The value that [value] will have after a [SemanticsAction.increase] action /// has been performed. /// @@ -1929,6 +1993,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { /// available on this node. /// /// The reading direction is given by [textDirection]. + /// + /// This exposes the raw text of the [attributedIncreasedValue]. String get increasedValue => _attributedIncreasedValue.string; /// The value in [AttributedString] format that [value] or [attributedValue] @@ -1938,19 +2004,47 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { /// available on this node. /// /// The reading direction is given by [textDirection]. + /// + /// See also [increasedValue], which exposes just the raw text. AttributedString get attributedIncreasedValue => _attributedIncreasedValue; AttributedString _attributedIncreasedValue = _kEmptyConfig.attributedIncreasedValue; + /// The value that [value] will have after a [SemanticsAction.decrease] action + /// has been performed. + /// + /// This property is only valid if the [SemanticsAction.decrease] action is + /// available on this node. + /// + /// The reading direction is given by [textDirection]. + /// + /// This exposes the raw text of the [attributedDecreasedValue]. + String get decreasedValue => _attributedDecreasedValue.string; + + /// The value in [AttributedString] format that [value] or [attributedValue] + /// will have after a [SemanticsAction.decrease] action has been performed. + /// + /// This property is only valid if the [SemanticsAction.decrease] action is + /// available on this node. + /// + /// The reading direction is given by [textDirection]. + /// + /// See also [decreasedValue], which exposes just the raw text. + AttributedString get attributedDecreasedValue => _attributedDecreasedValue; + AttributedString _attributedDecreasedValue = _kEmptyConfig.attributedDecreasedValue; /// A brief description of the result of performing an action on this node. /// /// The reading direction is given by [textDirection]. + /// + /// This exposes the raw text of the [attributedHint]. String get hint => _attributedHint.string; /// A brief description of the result of performing an action on this node /// in [AttributedString] format. /// /// The reading direction is given by [textDirection]. + /// + /// See also [hint], which exposes just the raw text. AttributedString get attributedHint => _attributedHint; AttributedString _attributedHint = _kEmptyConfig.attributedHint; @@ -2517,11 +2611,11 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { properties.add(IterableProperty('flags', flags, ifEmpty: null)); properties.add(FlagProperty('isInvisible', value: isInvisible, ifTrue: 'invisible')); properties.add(FlagProperty('isHidden', value: hasFlag(SemanticsFlag.isHidden), ifTrue: 'HIDDEN')); - properties.add(StringProperty('label', _attributedLabel.attributes.isEmpty ? _attributedLabel.string : _attributedLabel.toString(), defaultValue: '')); - properties.add(StringProperty('value', _attributedValue.attributes.isEmpty ? _attributedValue.string : _attributedValue.toString(), defaultValue: '')); - properties.add(StringProperty('increasedValue', _attributedIncreasedValue.attributes.isEmpty ? _attributedIncreasedValue.string : _attributedIncreasedValue.toString(), defaultValue: '')); - properties.add(StringProperty('decreasedValue', _attributedDecreasedValue.attributes.isEmpty ? _attributedDecreasedValue.string : _attributedDecreasedValue.toString(), defaultValue: '')); - properties.add(StringProperty('hint', _attributedHint.attributes.isEmpty ? _attributedHint.string : _attributedHint.toString(), defaultValue: '')); + properties.add(AttributedStringProperty('label', _attributedLabel)); + properties.add(AttributedStringProperty('value', _attributedValue)); + properties.add(AttributedStringProperty('increasedValue', _attributedIncreasedValue)); + properties.add(AttributedStringProperty('decreasedValue', _attributedDecreasedValue)); + properties.add(AttributedStringProperty('hint', _attributedHint)); properties.add(EnumProperty('textDirection', _textDirection, defaultValue: null)); properties.add(DiagnosticsProperty('sortKey', sortKey, defaultValue: null)); if (_textSelection?.isValid == true) @@ -3692,7 +3786,8 @@ class SemanticsConfiguration { /// The reading direction is given by [textDirection]. /// /// See also: - /// * [attributedLabel]: which is the [AttributedString] of this property. + /// + /// * [attributedLabel], which is the [AttributedString] of this property. String get label => _attributedLabel.string; set label(String label) { assert(label != null); @@ -3710,6 +3805,10 @@ class SemanticsConfiguration { /// concatenated value is then used as the `Text` description. /// /// The reading direction is given by [textDirection]. + /// + /// See also: + /// + /// * [label], which is the raw text of this property. AttributedString get attributedLabel => _attributedLabel; AttributedString _attributedLabel = AttributedString(''); set attributedLabel(AttributedString attributedLabel) { @@ -3726,10 +3825,10 @@ class SemanticsConfiguration { /// See also: /// /// * [attributedValue], which is the [AttributedString] of this property. - /// * [decreasedValue] and [attributedDecreasedValue], describes what - /// [value] will be after performing [SemanticsAction.decrease]. - /// * [increasedValue] and [attributedIncreasedValue], describes what + /// * [increasedValue] and [attributedIncreasedValue], which describe what /// [value] will be after performing [SemanticsAction.increase]. + /// * [decreasedValue] and [attributedDecreasedValue], which describe what + /// [value] will be after performing [SemanticsAction.decrease]. String get value => _attributedValue.string; set value(String value) { assert(value != null); @@ -3750,10 +3849,11 @@ class SemanticsConfiguration { /// /// See also: /// - /// * [attributedDecreasedValue], describes what [value] will be after - /// performing [SemanticsAction.decrease]. - /// * [attributedIncreasedValue], describes what [value] will be after + /// * [value], which is the raw text of this property. + /// * [attributedIncreasedValue], which describes what [value] will be after /// performing [SemanticsAction.increase]. + /// * [attributedDecreasedValue], which describes what [value] will be after + /// performing [SemanticsAction.decrease]. AttributedString get attributedValue => _attributedValue; AttributedString _attributedValue = AttributedString(''); set attributedValue(AttributedString attributedValue) { @@ -3761,38 +3861,6 @@ class SemanticsConfiguration { _hasBeenAnnotated = true; } - /// The value that [value] will have after performing a - /// [SemanticsAction.decrease] action. - /// - /// Setting this attribute will override the [attributedDecreasedValue]. - /// - /// One of the [attributedDecreasedValue] or [decreasedValue] must be set if - /// a handler for [SemanticsAction.decrease] is provided and one of the - /// [value] or [attributedValue] is set. - /// - /// The reading direction is given by [textDirection]. - String get decreasedValue => _attributedDecreasedValue.string; - set decreasedValue(String decreasedValue) { - assert(decreasedValue != null); - _attributedDecreasedValue = AttributedString(decreasedValue); - _hasBeenAnnotated = true; - } - - /// The value that [value] will have after performing a - /// [SemanticsAction.decrease] action in [AttributedString] format. - /// - /// One of the [attributedDecreasedValue] or [decreasedValue] must be set if - /// a handler for [SemanticsAction.decrease] is provided and one of the - /// [value] or [attributedValue] is set. - /// - /// The reading direction is given by [textDirection]. - AttributedString get attributedDecreasedValue => _attributedDecreasedValue; - AttributedString _attributedDecreasedValue = AttributedString(''); - set attributedDecreasedValue(AttributedString attributedDecreasedValue) { - _attributedDecreasedValue = attributedDecreasedValue; - _hasBeenAnnotated = true; - } - /// The value that [value] will have after performing a /// [SemanticsAction.increase] action. /// @@ -3803,6 +3871,10 @@ class SemanticsConfiguration { /// [value] or [attributedValue] is set. /// /// The reading direction is given by [textDirection]. + /// + /// See also: + /// + /// * [attributedIncreasedValue], which is the [AttributedString] of this property. String get increasedValue => _attributedIncreasedValue.string; set increasedValue(String increasedValue) { assert(increasedValue != null); @@ -3818,6 +3890,10 @@ class SemanticsConfiguration { /// [value] or [attributedValue] is set. /// /// The reading direction is given by [textDirection]. + /// + /// See also: + /// + /// * [increasedValue], which is the raw text of this property. AttributedString get attributedIncreasedValue => _attributedIncreasedValue; AttributedString _attributedIncreasedValue = AttributedString(''); set attributedIncreasedValue(AttributedString attributedIncreasedValue) { @@ -3825,6 +3901,43 @@ class SemanticsConfiguration { _hasBeenAnnotated = true; } + /// The value that [value] will have after performing a + /// [SemanticsAction.decrease] action. + /// + /// Setting this attribute will override the [attributedDecreasedValue]. + /// + /// One of the [attributedDecreasedValue] or [decreasedValue] must be set if + /// a handler for [SemanticsAction.decrease] is provided and one of the + /// [value] or [attributedValue] is set. + /// + /// The reading direction is given by [textDirection]. + /// + /// * [attributedDecreasedValue], which is the [AttributedString] of this property. + String get decreasedValue => _attributedDecreasedValue.string; + set decreasedValue(String decreasedValue) { + assert(decreasedValue != null); + _attributedDecreasedValue = AttributedString(decreasedValue); + _hasBeenAnnotated = true; + } + + /// The value that [value] will have after performing a + /// [SemanticsAction.decrease] action in [AttributedString] format. + /// + /// One of the [attributedDecreasedValue] or [decreasedValue] must be set if + /// a handler for [SemanticsAction.decrease] is provided and one of the + /// [value] or [attributedValue] is set. + /// + /// The reading direction is given by [textDirection]. + /// + /// See also: + /// + /// * [decreasedValue], which is the raw text of this property. + AttributedString get attributedDecreasedValue => _attributedDecreasedValue; + AttributedString _attributedDecreasedValue = AttributedString(''); + set attributedDecreasedValue(AttributedString attributedDecreasedValue) { + _attributedDecreasedValue = attributedDecreasedValue; + _hasBeenAnnotated = true; + } /// A brief description of the result of performing an action on this node. /// @@ -3833,7 +3946,8 @@ class SemanticsConfiguration { /// The reading direction is given by [textDirection]. /// /// See also: - /// * [attributedHint]: which is the [AttributedString] of this property. + /// + /// * [attributedHint], which is the [AttributedString] of this property. String get hint => _attributedHint.string; set hint(String hint) { assert(hint != null); @@ -3851,6 +3965,10 @@ class SemanticsConfiguration { /// concatenated value is then used as the `Text` description. /// /// The reading direction is given by [textDirection]. + /// + /// See also: + /// + /// * [hint], which is the raw text of this property. AttributedString get attributedHint => _attributedHint; AttributedString _attributedHint = AttributedString(''); set attributedHint(AttributedString attributedHint) { diff --git a/packages/flutter/test/semantics/semantics_test.dart b/packages/flutter/test/semantics/semantics_test.dart index 33e9f54201..096c59e4bc 100644 --- a/packages/flutter/test/semantics/semantics_test.dart +++ b/packages/flutter/test/semantics/semantics_test.dart @@ -69,6 +69,10 @@ void main() { expect(config.label, 'label1'); expect(config.attributedLabel.string, 'label1'); expect(config.attributedLabel.attributes.isEmpty, isTrue); + expect( + (SemanticsNode()..updateWith(config: config)).toString(), + 'SemanticsNode#1(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible, label: "label1")', + ); config.attributedLabel = AttributedString( 'label2', @@ -81,16 +85,28 @@ void main() { expect(config.attributedLabel.attributes.length, 1); expect(config.attributedLabel.attributes[0] is SpellOutStringAttribute, isTrue); expect(config.attributedLabel.attributes[0].range, const TextRange(start: 0, end: 1)); + expect( + (SemanticsNode()..updateWith(config: config)).toString(), + 'SemanticsNode#2(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible, label: "label2" [SpellOutStringAttribute(TextRange(start: 0, end: 1))])', + ); config.label = 'label3'; expect(config.label, 'label3'); expect(config.attributedLabel.string, 'label3'); expect(config.attributedLabel.attributes.isEmpty, isTrue); + expect( + (SemanticsNode()..updateWith(config: config)).toString(), + 'SemanticsNode#3(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible, label: "label3")', + ); config.value = 'value1'; expect(config.value, 'value1'); expect(config.attributedValue.string, 'value1'); expect(config.attributedValue.attributes.isEmpty, isTrue); + expect( + (SemanticsNode()..updateWith(config: config)).toString(), + 'SemanticsNode#4(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible, label: "label3", value: "value1")', + ); config.attributedValue = AttributedString( 'value2', @@ -103,16 +119,28 @@ void main() { expect(config.attributedValue.attributes.length, 1); expect(config.attributedValue.attributes[0] is SpellOutStringAttribute, isTrue); expect(config.attributedValue.attributes[0].range, const TextRange(start: 0, end: 1)); + expect( + (SemanticsNode()..updateWith(config: config)).toString(), + 'SemanticsNode#5(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible, label: "label3", value: "value2" [SpellOutStringAttribute(TextRange(start: 0, end: 1))])', + ); config.value = 'value3'; expect(config.value, 'value3'); expect(config.attributedValue.string, 'value3'); expect(config.attributedValue.attributes.isEmpty, isTrue); + expect( + (SemanticsNode()..updateWith(config: config)).toString(), + 'SemanticsNode#6(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible, label: "label3", value: "value3")', + ); config.hint = 'hint1'; expect(config.hint, 'hint1'); expect(config.attributedHint.string, 'hint1'); expect(config.attributedHint.attributes.isEmpty, isTrue); + expect( + (SemanticsNode()..updateWith(config: config)).toString(), + 'SemanticsNode#7(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible, label: "label3", value: "value3", hint: "hint1")', + ); config.attributedHint = AttributedString( 'hint2', @@ -125,11 +153,19 @@ void main() { expect(config.attributedHint.attributes.length, 1); expect(config.attributedHint.attributes[0] is SpellOutStringAttribute, isTrue); expect(config.attributedHint.attributes[0].range, const TextRange(start: 0, end: 1)); + expect( + (SemanticsNode()..updateWith(config: config)).toString(), + 'SemanticsNode#8(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible, label: "label3", value: "value3", hint: "hint2" [SpellOutStringAttribute(TextRange(start: 0, end: 1))])', + ); config.hint = 'hint3'; expect(config.hint, 'hint3'); expect(config.attributedHint.string, 'hint3'); expect(config.attributedHint.attributes.isEmpty, isTrue); + expect( + (SemanticsNode()..updateWith(config: config)).toString(), + 'SemanticsNode#9(STALE, owner: null, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), invisible, label: "label3", value: "value3", hint: "hint3")', + ); }); test('mutate existing semantic node list errors', () { @@ -654,6 +690,7 @@ void main() { expect(result.attributes.length, 2); expect(result.attributes[0].range, const TextRange(start:0, end:4)); expect(result.attributes[0] is SpellOutStringAttribute, isTrue); + expect(result.toString(), "AttributedString('string1string2', attributes: [SpellOutStringAttribute(TextRange(start: 0, end: 4)), LocaleStringAttribute(TextRange(start: 7, end: 11), es-MX)])"); }); test('Semantics id does not repeat', () { diff --git a/packages/flutter/test/semantics/semantics_update_test.dart b/packages/flutter/test/semantics/semantics_update_test.dart index 2f2b6abbaa..78b0926dee 100644 --- a/packages/flutter/test/semantics/semantics_update_test.dart +++ b/packages/flutter/test/semantics/semantics_update_test.dart @@ -151,6 +151,17 @@ void main() { expect(SemanticsUpdateBuilderSpy.observations[1]!.hintAttributes![0] is SpellOutStringAttribute, isTrue); expect(SemanticsUpdateBuilderSpy.observations[1]!.hintAttributes![0].range, const TextRange(start: 1, end: 2)); + expect( + tester.widget(find.byType(Semantics)).toString(), + 'Semantics(' + 'container: false, ' + '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 + ')', + ); + SemanticsUpdateBuilderSpy.observations.clear(); handle.dispose(); });