mirror of
https://github.com/flutter/flutter
synced 2024-10-13 11:42:54 +00:00
Support for accessibilityHint and accessibilityValue (#12677)
* Support accessibility labels and hints * more tests * ++ * review comments * fix merge * test fix
This commit is contained in:
parent
80b820a26d
commit
c3e049613d
|
@ -1 +1 @@
|
|||
8e79156765c67b71b1e1b9895dbc8eea43f9949c
|
||||
91071f817b2f6a0f6684e1f2fda3d8b21314bcb7
|
||||
|
|
|
@ -3176,6 +3176,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||
bool selected,
|
||||
bool button,
|
||||
String label,
|
||||
String value,
|
||||
String hint,
|
||||
TextDirection textDirection,
|
||||
}) : assert(container != null),
|
||||
_container = container,
|
||||
|
@ -3184,6 +3186,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||
_selected = selected,
|
||||
_button = button,
|
||||
_label = label,
|
||||
_value = value,
|
||||
_hint = hint,
|
||||
_textDirection = textDirection,
|
||||
super(child);
|
||||
|
||||
|
@ -3250,17 +3254,6 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.label] semantic to the given value.
|
||||
String get label => _label;
|
||||
String _label;
|
||||
set label(String value) {
|
||||
if (label == value)
|
||||
return;
|
||||
final bool hadValue = label != null;
|
||||
_label = value;
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.isButton] semantic to the given value.
|
||||
bool get button => _button;
|
||||
bool _button;
|
||||
|
@ -3272,9 +3265,48 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.label] semantic to the given value.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get label => _label;
|
||||
String _label;
|
||||
set label(String value) {
|
||||
if (_label == value)
|
||||
return;
|
||||
final bool hadValue = _label != null;
|
||||
_label = value;
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.value] semantic to the given value.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get value => _value;
|
||||
String _value;
|
||||
set value(String value) {
|
||||
if (_value == value)
|
||||
return;
|
||||
final bool hadValue = _value != null;
|
||||
_value = value;
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.hint] semantic to the given value.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get hint => _hint;
|
||||
String _hint;
|
||||
set hint(String value) {
|
||||
if (_hint == value)
|
||||
return;
|
||||
final bool hadValue = _hint != null;
|
||||
_hint = value;
|
||||
markNeedsSemanticsUpdate(onlyLocalUpdates: (value != null) == hadValue);
|
||||
}
|
||||
|
||||
/// If non-null, sets the [SemanticsNode.textDirection] semantic to the given value.
|
||||
///
|
||||
/// This must not be null if [label] is not null.
|
||||
/// This must not be null if [label], [hint], or [value] is not null.
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection value) {
|
||||
|
@ -3296,6 +3328,10 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||
config.isSelected = selected;
|
||||
if (label != null)
|
||||
config.label = label;
|
||||
if (value != null)
|
||||
config.value = value;
|
||||
if (hint != null)
|
||||
config.hint = hint;
|
||||
if (textDirection != null)
|
||||
config.textDirection = textDirection;
|
||||
if (button != null)
|
||||
|
|
|
@ -76,6 +76,8 @@ class SemanticsData extends Diagnosticable {
|
|||
@required this.flags,
|
||||
@required this.actions,
|
||||
@required this.label,
|
||||
@required this.value,
|
||||
@required this.hint,
|
||||
@required this.textDirection,
|
||||
@required this.rect,
|
||||
this.tags,
|
||||
|
@ -97,7 +99,17 @@ class SemanticsData extends Diagnosticable {
|
|||
/// The text's reading direction is given by [textDirection].
|
||||
final String label;
|
||||
|
||||
/// The reading direction for the text in [label].
|
||||
/// A textual description for the current value of the node.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
final String value;
|
||||
|
||||
/// A brief description of the result of performing an action on this node.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
final String hint;
|
||||
|
||||
/// The reading direction for the text in [label], [value], and [hint].
|
||||
final TextDirection textDirection;
|
||||
|
||||
/// The bounding box for this node in its coordinate system.
|
||||
|
@ -485,6 +497,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||
|
||||
bool _isDifferentFromCurrentSemanticAnnotation(SemanticsConfiguration config) {
|
||||
return _label != config.label ||
|
||||
_hint != config.hint ||
|
||||
_value != config.value ||
|
||||
_flags != config._flags ||
|
||||
_textDirection != config.textDirection ||
|
||||
_actionsAsBits != config._actionsAsBits ||
|
||||
|
@ -516,7 +530,19 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||
String get label => _label;
|
||||
String _label = _kEmptyConfig.label;
|
||||
|
||||
/// The reading direction for [label].
|
||||
/// A textual description for the current value of the node.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get value => _value;
|
||||
String _value = _kEmptyConfig.value;
|
||||
|
||||
/// A brief description of the result of performing an action on this node.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get hint => _hint;
|
||||
String _hint = _kEmptyConfig.hint;
|
||||
|
||||
/// The reading direction for [label], [value], and [hint].
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection = _kEmptyConfig.textDirection;
|
||||
|
||||
|
@ -533,6 +559,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||
_markDirty();
|
||||
|
||||
_label = config.label;
|
||||
_value = config.value;
|
||||
_hint = config.hint;
|
||||
_flags = config._flags;
|
||||
_textDirection = config.textDirection;
|
||||
_actions = new Map<SemanticsAction, VoidCallback>.from(config._actions);
|
||||
|
@ -551,6 +579,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||
int flags = _flags;
|
||||
int actions = _actionsAsBits;
|
||||
String label = _label;
|
||||
String hint = _hint;
|
||||
String value = _value;
|
||||
TextDirection textDirection = _textDirection;
|
||||
Set<SemanticsTag> mergedTags = tags == null ? null : new Set<SemanticsTag>.from(tags);
|
||||
|
||||
|
@ -560,27 +590,24 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||
flags |= node._flags;
|
||||
actions |= node._actionsAsBits;
|
||||
textDirection ??= node._textDirection;
|
||||
if (value == '' || value == null)
|
||||
value = node._value;
|
||||
if (node.tags != null) {
|
||||
mergedTags ??= new Set<SemanticsTag>();
|
||||
mergedTags.addAll(node.tags);
|
||||
}
|
||||
if (node._label.isNotEmpty) {
|
||||
String nestedLabel = node._label;
|
||||
if (textDirection != node._textDirection && node._textDirection != null) {
|
||||
switch (node._textDirection) {
|
||||
case TextDirection.rtl:
|
||||
nestedLabel = '${Unicode.RLE}$nestedLabel${Unicode.PDF}';
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
nestedLabel = '${Unicode.LRE}$nestedLabel${Unicode.PDF}';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (label.isEmpty)
|
||||
label = nestedLabel;
|
||||
else
|
||||
label = '$label\n$nestedLabel';
|
||||
}
|
||||
label = _concatStrings(
|
||||
thisString: label,
|
||||
thisTextDirection: textDirection,
|
||||
otherString: node._label,
|
||||
otherTextDirection: node._textDirection,
|
||||
);
|
||||
hint = _concatStrings(
|
||||
thisString: hint,
|
||||
thisTextDirection: textDirection,
|
||||
otherString: node._hint,
|
||||
otherTextDirection: node._textDirection,
|
||||
);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
@ -589,6 +616,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||
flags: flags,
|
||||
actions: actions,
|
||||
label: label,
|
||||
value: value,
|
||||
hint: hint,
|
||||
textDirection: textDirection,
|
||||
rect: rect,
|
||||
transform: transform,
|
||||
|
@ -621,6 +650,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||
actions: data.actions,
|
||||
rect: data.rect,
|
||||
label: data.label,
|
||||
value: data.value,
|
||||
hint: data.hint,
|
||||
textDirection: data.textDirection,
|
||||
transform: data.transform?.storage ?? _kIdentityTransform,
|
||||
children: children,
|
||||
|
@ -676,8 +707,10 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||
if (_hasFlag(SemanticsFlags.hasCheckedState))
|
||||
properties.add(new FlagProperty('isChecked', value: _hasFlag(SemanticsFlags.isChecked), ifTrue: 'checked', ifFalse: 'unchecked'));
|
||||
properties.add(new FlagProperty('isSelected', value: _hasFlag(SemanticsFlags.isSelected), ifTrue: 'selected'));
|
||||
properties.add(new StringProperty('label', _label, defaultValue: ''));
|
||||
properties.add(new FlagProperty('isButton', value: _hasFlag(SemanticsFlags.isButton), ifTrue: 'button'));
|
||||
properties.add(new StringProperty('label', _label, defaultValue: ''));
|
||||
properties.add(new StringProperty('value', _value, defaultValue: ''));
|
||||
properties.add(new StringProperty('hint', _hint, defaultValue: ''));
|
||||
properties.add(new EnumProperty<TextDirection>('textDirection', _textDirection, defaultValue: null));
|
||||
}
|
||||
|
||||
|
@ -1015,6 +1048,11 @@ class SemanticsConfiguration {
|
|||
|
||||
/// A textual description of the owning [RenderObject].
|
||||
///
|
||||
/// On iOS this is used for the `accessibilityLabel` property defined in the
|
||||
/// `UIAccessibility` Protocol. On Android it is concatenated together with
|
||||
/// [value] and [hint] in the following order: [value], [label], [hint].
|
||||
/// The concatenated value is then used as the `Text` description.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get label => _label;
|
||||
String _label = '';
|
||||
|
@ -1023,7 +1061,37 @@ class SemanticsConfiguration {
|
|||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// The reading direction for the text in [label].
|
||||
/// A textual description for the current value of the owning [RenderObject].
|
||||
///
|
||||
/// On iOS this is used for the `accessibilityValue` property defined in the
|
||||
/// `UIAccessibility` Protocol. On Android it is concatenated together with
|
||||
/// [label] and [hint] in the following order: [value], [label], [hint].
|
||||
/// The concatenated value is then used as the `Text` description.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get value => _value;
|
||||
String _value = '';
|
||||
set value(String value) {
|
||||
_value = value;
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// A brief description of the result of performing an action on this node.
|
||||
///
|
||||
/// On iOS this is used for the `accessibilityHint` property defined in the
|
||||
/// `UIAccessibility` Protocol. On Android it is concatenated together with
|
||||
/// [label] and [value] in the following order: [value], [label], [hint].
|
||||
/// The concatenated value is then used as the `Text` description.
|
||||
///
|
||||
/// The text's reading direction is given by [textDirection].
|
||||
String get hint => _hint;
|
||||
String _hint = '';
|
||||
set hint(String hint) {
|
||||
_hint = hint;
|
||||
_hasBeenAnnotated = true;
|
||||
}
|
||||
|
||||
/// The reading direction for the text in [label], [value], and [hint].
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
set textDirection(TextDirection textDirection) {
|
||||
|
@ -1087,6 +1155,8 @@ class SemanticsConfiguration {
|
|||
return false;
|
||||
if ((_flags & other._flags) != 0)
|
||||
return false;
|
||||
if (_value != null && _value.isNotEmpty && other._value != null && other._value.isNotEmpty)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1109,23 +1179,20 @@ class SemanticsConfiguration {
|
|||
_flags |= other._flags;
|
||||
|
||||
textDirection ??= other.textDirection;
|
||||
if (other.label.isNotEmpty) {
|
||||
String nestedLabel = other.label;
|
||||
if (textDirection != other.textDirection && other.textDirection != null) {
|
||||
switch (other.textDirection) {
|
||||
case TextDirection.rtl:
|
||||
nestedLabel = '${Unicode.RLE}$nestedLabel${Unicode.PDF}';
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
nestedLabel = '${Unicode.LRE}$nestedLabel${Unicode.PDF}';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (label.isEmpty)
|
||||
label = nestedLabel;
|
||||
else
|
||||
label = '$label\n$nestedLabel';
|
||||
}
|
||||
_label = _concatStrings(
|
||||
thisString: _label,
|
||||
thisTextDirection: textDirection,
|
||||
otherString: other._label,
|
||||
otherTextDirection: other.textDirection,
|
||||
);
|
||||
if (_value == '' || _value == null)
|
||||
_value = other._value;
|
||||
_hint = _concatStrings(
|
||||
thisString: _hint,
|
||||
thisTextDirection: textDirection,
|
||||
otherString: other._hint,
|
||||
otherTextDirection: other.textDirection,
|
||||
);
|
||||
|
||||
_hasBeenAnnotated = _hasBeenAnnotated || other._hasBeenAnnotated;
|
||||
}
|
||||
|
@ -1138,8 +1205,34 @@ class SemanticsConfiguration {
|
|||
.._hasBeenAnnotated = _hasBeenAnnotated
|
||||
.._textDirection = _textDirection
|
||||
.._label = _label
|
||||
.._value = _value
|
||||
.._hint = _hint
|
||||
.._flags = _flags
|
||||
.._actionsAsBits = _actionsAsBits
|
||||
.._actions.addAll(_actions);
|
||||
}
|
||||
}
|
||||
|
||||
String _concatStrings({
|
||||
@required String thisString,
|
||||
@required String otherString,
|
||||
@required TextDirection thisTextDirection,
|
||||
@required TextDirection otherTextDirection
|
||||
}) {
|
||||
if (otherString.isEmpty)
|
||||
return thisString;
|
||||
String nestedLabel = otherString;
|
||||
if (thisTextDirection != otherTextDirection && otherTextDirection != null) {
|
||||
switch (otherTextDirection) {
|
||||
case TextDirection.rtl:
|
||||
nestedLabel = '${Unicode.RLE}$nestedLabel${Unicode.PDF}';
|
||||
break;
|
||||
case TextDirection.ltr:
|
||||
nestedLabel = '${Unicode.LRE}$nestedLabel${Unicode.PDF}';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (thisString.isEmpty)
|
||||
return nestedLabel;
|
||||
return '$thisString\n$nestedLabel';
|
||||
}
|
||||
|
|
|
@ -4453,6 +4453,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||
this.selected,
|
||||
this.button,
|
||||
this.label,
|
||||
this.value,
|
||||
this.hint,
|
||||
this.textDirection,
|
||||
}) : assert(container != null),
|
||||
super(key: key, child: child);
|
||||
|
@ -4502,15 +4504,40 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||
///
|
||||
/// If a label is provided, there must either by an ambient [Directionality]
|
||||
/// or an explicit [textDirection] should be provided.
|
||||
///
|
||||
/// See also:
|
||||
/// * [SemanticsConfiguration.label] for a description of how this is exposed
|
||||
/// in TalkBack and VoiceOver.
|
||||
final String label;
|
||||
|
||||
/// The reading direction of the [label].
|
||||
/// Provides a textual description of the value of the widget.
|
||||
///
|
||||
/// If a value is provided, there must either by an ambient [Directionality]
|
||||
/// or an explicit [textDirection] should be provided.
|
||||
///
|
||||
/// See also:
|
||||
/// * [SemanticsConfiguration.value] for a description of how this is exposed
|
||||
/// in TalkBack and VoiceOver.
|
||||
final String value;
|
||||
|
||||
/// Provides a brief textual description of the result of an action performed
|
||||
/// on the widget.
|
||||
///
|
||||
/// If a hint is provided, there must either by an ambient [Directionality]
|
||||
/// or an explicit [textDirection] should be provided.
|
||||
///
|
||||
/// See also:
|
||||
/// * [SemanticsConfiguration.hint] for a description of how this is exposed
|
||||
/// in TalkBack and VoiceOver.
|
||||
final String hint;
|
||||
|
||||
/// The reading direction of the [label], [value], and [hint].
|
||||
///
|
||||
/// Defaults to the ambient [Directionality].
|
||||
final TextDirection textDirection;
|
||||
|
||||
TextDirection _getTextDirection(BuildContext context) {
|
||||
return textDirection ?? (label != null ? Directionality.of(context) : null);
|
||||
return textDirection ?? (label != null || value != null || hint != null ? Directionality.of(context) : null);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -4520,8 +4547,10 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||
explicitChildNodes: explicitChildNodes,
|
||||
checked: checked,
|
||||
selected: selected,
|
||||
label: label,
|
||||
button: button,
|
||||
label: label,
|
||||
value: value,
|
||||
hint: hint,
|
||||
textDirection: _getTextDirection(context),
|
||||
);
|
||||
}
|
||||
|
@ -4534,6 +4563,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||
..checked = checked
|
||||
..selected = selected
|
||||
..label = label
|
||||
..value = value
|
||||
..hint = hint
|
||||
..textDirection = _getTextDirection(context);
|
||||
}
|
||||
|
||||
|
@ -4544,6 +4575,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||
description.add(new DiagnosticsProperty<bool>('checked', checked, defaultValue: null));
|
||||
description.add(new DiagnosticsProperty<bool>('selected', selected, defaultValue: null));
|
||||
description.add(new StringProperty('label', label, defaultValue: ''));
|
||||
description.add(new StringProperty('value', value));
|
||||
description.add(new StringProperty('hint', hint));
|
||||
description.add(new EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,7 +198,7 @@ void main() {
|
|||
|
||||
expect(
|
||||
minimalProperties.toStringDeep(minLevel: DiagnosticLevel.hidden),
|
||||
'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, label: "", isButton: false, textDirection: null)\n',
|
||||
'SemanticsNode#16(owner: null, isPartOfNodeMerging: false, Rect.fromLTRB(0.0, 0.0, 0.0, 0.0), actions: [], isSelected: false, isButton: false, label: "", value: "", hint: "", textDirection: null)\n',
|
||||
);
|
||||
|
||||
final SemanticsConfiguration config = new SemanticsConfiguration()
|
||||
|
@ -217,7 +217,7 @@ void main() {
|
|||
..updateWith(config: config, childrenInInversePaintOrder: null);
|
||||
expect(
|
||||
allProperties.toStringDeep(),
|
||||
'SemanticsNode#17(STALE, owner: null, leaf merge, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), actions: [longPress, scrollUp, showOnScreen], unchecked, selected, label: "Use all the properties", button, textDirection: rtl)\n',
|
||||
'SemanticsNode#17(STALE, owner: null, leaf merge, Rect.fromLTRB(60.0, 20.0, 80.0, 50.0), actions: [longPress, scrollUp, showOnScreen], unchecked, selected, button, label: "Use all the properties", textDirection: rtl)\n',
|
||||
);
|
||||
expect(
|
||||
allProperties.getSemanticsData().toString(),
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
|
@ -220,4 +221,153 @@ void main() {
|
|||
|
||||
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
|
||||
});
|
||||
|
||||
testWidgets('Semantics label and hint', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Semantics(
|
||||
label: 'label',
|
||||
hint: 'hint',
|
||||
value: 'value',
|
||||
child: new Container(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
label: 'label',
|
||||
hint: 'hint',
|
||||
value: 'value',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
|
||||
});
|
||||
|
||||
testWidgets('Semantics hints can merge', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Semantics(
|
||||
container: true,
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
const Semantics(
|
||||
hint: 'hint one',
|
||||
),
|
||||
const Semantics(
|
||||
hint: 'hint two',
|
||||
)
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
hint: 'hint one\nhint two',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
|
||||
});
|
||||
|
||||
testWidgets('Semantics values do not merge', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Semantics(
|
||||
container: true,
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
new Semantics(
|
||||
value: 'value one',
|
||||
child: new Container(
|
||||
height: 10.0,
|
||||
width: 10.0,
|
||||
)
|
||||
),
|
||||
new Semantics(
|
||||
value: 'value two',
|
||||
child: new Container(
|
||||
height: 10.0,
|
||||
width: 10.0,
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
value: 'value one',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
new TestSemantics(
|
||||
value: 'value two',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
]
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
|
||||
});
|
||||
|
||||
testWidgets('Semantics value and hint can merge', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: new Semantics(
|
||||
container: true,
|
||||
child: new Column(
|
||||
children: <Widget>[
|
||||
const Semantics(
|
||||
hint: 'hint',
|
||||
),
|
||||
const Semantics(
|
||||
value: 'value',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestSemantics expectedSemantics = new TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics.rootChild(
|
||||
hint: 'hint',
|
||||
value: 'value',
|
||||
textDirection: TextDirection.ltr,
|
||||
)
|
||||
]
|
||||
);
|
||||
|
||||
expect(semantics, hasSemantics(expectedSemantics, ignoreTransform: true, ignoreRect: true, ignoreId: true));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ class TestSemantics {
|
|||
this.flags: 0,
|
||||
this.actions: 0,
|
||||
this.label: '',
|
||||
this.value: '',
|
||||
this.hint: '',
|
||||
this.textDirection,
|
||||
this.rect,
|
||||
this.transform,
|
||||
|
@ -42,6 +44,8 @@ class TestSemantics {
|
|||
Iterable<SemanticsTag> tags,
|
||||
}) : assert(flags != null),
|
||||
assert(label != null),
|
||||
assert(value != null),
|
||||
assert(hint != null),
|
||||
assert(children != null),
|
||||
tags = tags?.toSet() ?? new Set<SemanticsTag>();
|
||||
|
||||
|
@ -51,6 +55,8 @@ class TestSemantics {
|
|||
this.flags: 0,
|
||||
this.actions: 0,
|
||||
this.label: '',
|
||||
this.value: '',
|
||||
this.hint: '',
|
||||
this.textDirection,
|
||||
this.transform,
|
||||
this.children: const <TestSemantics>[],
|
||||
|
@ -58,6 +64,8 @@ class TestSemantics {
|
|||
}) : id = 0,
|
||||
assert(flags != null),
|
||||
assert(label != null),
|
||||
assert(value != null),
|
||||
assert(hint != null),
|
||||
rect = TestSemantics.rootRect,
|
||||
assert(children != null),
|
||||
tags = tags?.toSet() ?? new Set<SemanticsTag>();
|
||||
|
@ -76,6 +84,8 @@ class TestSemantics {
|
|||
this.flags: 0,
|
||||
this.actions: 0,
|
||||
this.label: '',
|
||||
this.hint: '',
|
||||
this.value: '',
|
||||
this.textDirection,
|
||||
this.rect,
|
||||
Matrix4 transform,
|
||||
|
@ -83,6 +93,8 @@ class TestSemantics {
|
|||
Iterable<SemanticsTag> tags,
|
||||
}) : assert(flags != null),
|
||||
assert(label != null),
|
||||
assert(value != null),
|
||||
assert(hint != null),
|
||||
transform = _applyRootChildScale(transform),
|
||||
assert(children != null),
|
||||
tags = tags?.toSet() ?? new Set<SemanticsTag>();
|
||||
|
@ -102,6 +114,13 @@ class TestSemantics {
|
|||
/// A textual description of this node.
|
||||
final String label;
|
||||
|
||||
/// A textual description for the value of this node.
|
||||
final String value;
|
||||
|
||||
/// A brief textual description of the result of the action that can be
|
||||
/// performed on this node.
|
||||
final String hint;
|
||||
|
||||
/// The reading direction of the [label].
|
||||
///
|
||||
/// Even if this is not set, the [hasSemantics] matcher will verify that if a
|
||||
|
@ -169,10 +188,14 @@ class TestSemantics {
|
|||
return fail('expected node id $id to have actions $actions but found actions ${nodeData.actions}.');
|
||||
if (label != nodeData.label)
|
||||
return fail('expected node id $id to have label "$label" but found label "${nodeData.label}".');
|
||||
if (value != nodeData.value)
|
||||
return fail('expected node id $id to have value "$value" but found value "${nodeData.value}".');
|
||||
if (hint != nodeData.hint)
|
||||
return fail('expected node id $id to have hint "$hint" but found hint "${nodeData.hint}".');
|
||||
if (textDirection != null && textDirection != nodeData.textDirection)
|
||||
return fail('expected node id $id to have textDirection "$textDirection" but found "${nodeData.textDirection}".');
|
||||
if (nodeData.label != '' && nodeData.textDirection == null)
|
||||
return fail('expected node id $id, which has a label, to have a textDirection, but it did not.');
|
||||
if ((nodeData.label != '' || nodeData.value != '' || nodeData.hint != '') && nodeData.textDirection == null)
|
||||
return fail('expected node id $id, which has a label, value, or hint, to have a textDirection, but it did not.');
|
||||
if (!ignoreRect && rect != nodeData.rect)
|
||||
return fail('expected node id $id to have rect $rect but found rect ${nodeData.rect}.');
|
||||
if (!ignoreTransform && transform != nodeData.transform)
|
||||
|
|
Loading…
Reference in a new issue