mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
This commit is contained in:
parent
f19f040a73
commit
4373a31971
@ -64,7 +64,11 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('TextField has correct Android semantics', () async {
|
test('TextField has correct Android semantics', () async {
|
||||||
final SerializableFinder normalTextField = find.byValueKey(normalTextFieldKeyValue);
|
final SerializableFinder normalTextField = find.descendant(
|
||||||
|
of: find.byValueKey(normalTextFieldKeyValue),
|
||||||
|
matching: find.byType('Semantics'),
|
||||||
|
firstMatchOnly: true,
|
||||||
|
);
|
||||||
expect(await getSemantics(normalTextField), hasAndroidSemantics(
|
expect(await getSemantics(normalTextField), hasAndroidSemantics(
|
||||||
className: AndroidClassName.editText,
|
className: AndroidClassName.editText,
|
||||||
isEditable: true,
|
isEditable: true,
|
||||||
@ -112,7 +116,11 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('password TextField has correct Android semantics', () async {
|
test('password TextField has correct Android semantics', () async {
|
||||||
final SerializableFinder passwordTextField = find.byValueKey(passwordTextFieldKeyValue);
|
final SerializableFinder passwordTextField = find.descendant(
|
||||||
|
of: find.byValueKey(passwordTextFieldKeyValue),
|
||||||
|
matching: find.byType('Semantics'),
|
||||||
|
firstMatchOnly: true,
|
||||||
|
);
|
||||||
expect(await getSemantics(passwordTextField), hasAndroidSemantics(
|
expect(await getSemantics(passwordTextField), hasAndroidSemantics(
|
||||||
className: AndroidClassName.editText,
|
className: AndroidClassName.editText,
|
||||||
isEditable: true,
|
isEditable: true,
|
||||||
|
@ -753,6 +753,8 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
|
|
||||||
bool get _isEnabled => widget.enabled ?? widget.decoration?.enabled ?? true;
|
bool get _isEnabled => widget.enabled ?? widget.decoration?.enabled ?? true;
|
||||||
|
|
||||||
|
int get _currentLength => _effectiveController.value.text.runes.length;
|
||||||
|
|
||||||
InputDecoration _getEffectiveDecoration() {
|
InputDecoration _getEffectiveDecoration() {
|
||||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||||
final ThemeData themeData = Theme.of(context);
|
final ThemeData themeData = Theme.of(context);
|
||||||
@ -769,7 +771,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
|
|
||||||
// If buildCounter was provided, use it to generate a counter widget.
|
// If buildCounter was provided, use it to generate a counter widget.
|
||||||
Widget counter;
|
Widget counter;
|
||||||
final int currentLength = _effectiveController.value.text.runes.length;
|
final int currentLength = _currentLength;
|
||||||
if (effectiveDecoration.counter == null
|
if (effectiveDecoration.counter == null
|
||||||
&& effectiveDecoration.counterText == null
|
&& effectiveDecoration.counterText == null
|
||||||
&& widget.buildCounter != null) {
|
&& widget.buildCounter != null) {
|
||||||
@ -1099,18 +1101,27 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
|
|||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return IgnorePointer(
|
||||||
|
ignoring: !_isEnabled,
|
||||||
|
child: MouseRegion(
|
||||||
|
onEnter: _handleMouseEnter,
|
||||||
|
onExit: _handleMouseExit,
|
||||||
|
child: AnimatedBuilder(
|
||||||
|
animation: controller, // changes the _currentLength
|
||||||
|
builder: (BuildContext context, Widget child) {
|
||||||
return Semantics(
|
return Semantics(
|
||||||
|
maxValueLength: widget.maxLengthEnforced && widget.maxLength != null && widget.maxLength > 0
|
||||||
|
? widget.maxLength
|
||||||
|
: null,
|
||||||
|
currentValueLength: _currentLength,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (!_effectiveController.selection.isValid)
|
if (!_effectiveController.selection.isValid)
|
||||||
_effectiveController.selection = TextSelection.collapsed(offset: _effectiveController.text.length);
|
_effectiveController.selection = TextSelection.collapsed(offset: _effectiveController.text.length);
|
||||||
_requestKeyboard();
|
_requestKeyboard();
|
||||||
},
|
},
|
||||||
child: MouseRegion(
|
child: child,
|
||||||
onEnter: _handleMouseEnter,
|
);
|
||||||
onExit: _handleMouseExit,
|
},
|
||||||
child: IgnorePointer(
|
|
||||||
ignoring: !_isEnabled,
|
|
||||||
child: _selectionGestureDetectorBuilder.buildGestureDetector(
|
child: _selectionGestureDetectorBuilder.buildGestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
child: child,
|
child: child,
|
||||||
|
@ -864,6 +864,12 @@ class RenderCustomPaint extends RenderProxyBox {
|
|||||||
if (properties.liveRegion != null) {
|
if (properties.liveRegion != null) {
|
||||||
config.liveRegion = properties.liveRegion;
|
config.liveRegion = properties.liveRegion;
|
||||||
}
|
}
|
||||||
|
if (properties.maxValueLength != null) {
|
||||||
|
config.maxValueLength = properties.maxValueLength;
|
||||||
|
}
|
||||||
|
if (properties.currentValueLength != null) {
|
||||||
|
config.currentValueLength = properties.currentValueLength;
|
||||||
|
}
|
||||||
if (properties.toggled != null) {
|
if (properties.toggled != null) {
|
||||||
config.isToggled = properties.toggled;
|
config.isToggled = properties.toggled;
|
||||||
}
|
}
|
||||||
|
@ -3497,6 +3497,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||||||
bool hidden,
|
bool hidden,
|
||||||
bool image,
|
bool image,
|
||||||
bool liveRegion,
|
bool liveRegion,
|
||||||
|
int maxValueLength,
|
||||||
|
int currentValueLength,
|
||||||
String label,
|
String label,
|
||||||
String value,
|
String value,
|
||||||
String increasedValue,
|
String increasedValue,
|
||||||
@ -3544,6 +3546,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||||||
_scopesRoute = scopesRoute,
|
_scopesRoute = scopesRoute,
|
||||||
_namesRoute = namesRoute,
|
_namesRoute = namesRoute,
|
||||||
_liveRegion = liveRegion,
|
_liveRegion = liveRegion,
|
||||||
|
_maxValueLength = maxValueLength,
|
||||||
|
_currentValueLength = currentValueLength,
|
||||||
_hidden = hidden,
|
_hidden = hidden,
|
||||||
_image = image,
|
_image = image,
|
||||||
_onDismiss = onDismiss,
|
_onDismiss = onDismiss,
|
||||||
@ -3799,6 +3803,28 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||||||
markNeedsSemanticsUpdate();
|
markNeedsSemanticsUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// If non-null, sets the [SemanticsNode.maxValueLength] semantic to the given
|
||||||
|
/// value.
|
||||||
|
int get maxValueLength => _maxValueLength;
|
||||||
|
int _maxValueLength;
|
||||||
|
set maxValueLength(int value) {
|
||||||
|
if (_maxValueLength == value)
|
||||||
|
return;
|
||||||
|
_maxValueLength = value;
|
||||||
|
markNeedsSemanticsUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If non-null, sets the [SemanticsNode.currentValueLength] semantic to the
|
||||||
|
/// given value.
|
||||||
|
int get currentValueLength => _currentValueLength;
|
||||||
|
int _currentValueLength;
|
||||||
|
set currentValueLength(int value) {
|
||||||
|
if (_currentValueLength == value)
|
||||||
|
return;
|
||||||
|
_currentValueLength = value;
|
||||||
|
markNeedsSemanticsUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
/// If non-null, sets the [SemanticsNode.isToggled] semantic to the given
|
/// If non-null, sets the [SemanticsNode.isToggled] semantic to the given
|
||||||
/// value.
|
/// value.
|
||||||
bool get toggled => _toggled;
|
bool get toggled => _toggled;
|
||||||
@ -4370,6 +4396,12 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
|
|||||||
config.namesRoute = namesRoute;
|
config.namesRoute = namesRoute;
|
||||||
if (liveRegion != null)
|
if (liveRegion != null)
|
||||||
config.liveRegion = liveRegion;
|
config.liveRegion = liveRegion;
|
||||||
|
if (maxValueLength != null) {
|
||||||
|
config.maxValueLength = maxValueLength;
|
||||||
|
}
|
||||||
|
if (currentValueLength != null) {
|
||||||
|
config.currentValueLength = currentValueLength;
|
||||||
|
}
|
||||||
if (textDirection != null)
|
if (textDirection != null)
|
||||||
config.textDirection = textDirection;
|
config.textDirection = textDirection;
|
||||||
if (sortKey != null)
|
if (sortKey != null)
|
||||||
|
@ -197,6 +197,8 @@ class SemanticsData extends Diagnosticable {
|
|||||||
@required this.scrollExtentMax,
|
@required this.scrollExtentMax,
|
||||||
@required this.scrollExtentMin,
|
@required this.scrollExtentMin,
|
||||||
@required this.platformViewId,
|
@required this.platformViewId,
|
||||||
|
@required this.maxValueLength,
|
||||||
|
@required this.currentValueLength,
|
||||||
this.tags,
|
this.tags,
|
||||||
this.transform,
|
this.transform,
|
||||||
this.customSemanticsActionIds,
|
this.customSemanticsActionIds,
|
||||||
@ -309,6 +311,26 @@ class SemanticsData extends Diagnosticable {
|
|||||||
/// * [UiKitView], which is the platform view for iOS.
|
/// * [UiKitView], which is the platform view for iOS.
|
||||||
final int platformViewId;
|
final int platformViewId;
|
||||||
|
|
||||||
|
/// The maximum number of characters that can be entered into an editable
|
||||||
|
/// text field.
|
||||||
|
///
|
||||||
|
/// For the purpose of this function a character is defined as one Unicode
|
||||||
|
/// scalar value.
|
||||||
|
///
|
||||||
|
/// This should only be set when [SemanticsFlag.isTextField] is set. Defaults
|
||||||
|
/// to null, which means no limit is imposed on the text field.
|
||||||
|
final int maxValueLength;
|
||||||
|
|
||||||
|
/// The current number of characters that have been entered into an editable
|
||||||
|
/// text field.
|
||||||
|
///
|
||||||
|
/// For the purpose of this function a character is defined as one Unicode
|
||||||
|
/// scalar value.
|
||||||
|
///
|
||||||
|
/// This should only be set when [SemanticsFlag.isTextField] is set. This must
|
||||||
|
/// be set when [maxValueLength] is set.
|
||||||
|
final int currentValueLength;
|
||||||
|
|
||||||
/// The bounding box for this node in its coordinate system.
|
/// The bounding box for this node in its coordinate system.
|
||||||
final Rect rect;
|
final Rect rect;
|
||||||
|
|
||||||
@ -389,6 +411,8 @@ class SemanticsData extends Diagnosticable {
|
|||||||
if (textSelection?.isValid == true)
|
if (textSelection?.isValid == true)
|
||||||
properties.add(MessageProperty('textSelection', '[${textSelection.start}, ${textSelection.end}]'));
|
properties.add(MessageProperty('textSelection', '[${textSelection.start}, ${textSelection.end}]'));
|
||||||
properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
|
properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
|
||||||
|
properties.add(IntProperty('maxValueLength', maxValueLength, defaultValue: null));
|
||||||
|
properties.add(IntProperty('currentValueLength', currentValueLength, defaultValue: null));
|
||||||
properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
|
properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
|
||||||
properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
|
properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
|
||||||
properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
|
properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
|
||||||
@ -418,6 +442,8 @@ class SemanticsData extends Diagnosticable {
|
|||||||
&& typedOther.scrollExtentMax == scrollExtentMax
|
&& typedOther.scrollExtentMax == scrollExtentMax
|
||||||
&& typedOther.scrollExtentMin == scrollExtentMin
|
&& typedOther.scrollExtentMin == scrollExtentMin
|
||||||
&& typedOther.platformViewId == platformViewId
|
&& typedOther.platformViewId == platformViewId
|
||||||
|
&& typedOther.maxValueLength == maxValueLength
|
||||||
|
&& typedOther.currentValueLength == currentValueLength
|
||||||
&& typedOther.transform == transform
|
&& typedOther.transform == transform
|
||||||
&& typedOther.elevation == elevation
|
&& typedOther.elevation == elevation
|
||||||
&& typedOther.thickness == thickness
|
&& typedOther.thickness == thickness
|
||||||
@ -445,10 +471,12 @@ class SemanticsData extends Diagnosticable {
|
|||||||
scrollExtentMax,
|
scrollExtentMax,
|
||||||
scrollExtentMin,
|
scrollExtentMin,
|
||||||
platformViewId,
|
platformViewId,
|
||||||
|
maxValueLength,
|
||||||
|
currentValueLength,
|
||||||
transform,
|
transform,
|
||||||
|
),
|
||||||
elevation,
|
elevation,
|
||||||
thickness,
|
thickness,
|
||||||
),
|
|
||||||
ui.hashList(customSemanticsActionIds),
|
ui.hashList(customSemanticsActionIds),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -575,6 +603,8 @@ class SemanticsProperties extends DiagnosticableTree {
|
|||||||
this.namesRoute,
|
this.namesRoute,
|
||||||
this.image,
|
this.image,
|
||||||
this.liveRegion,
|
this.liveRegion,
|
||||||
|
this.maxValueLength,
|
||||||
|
this.currentValueLength,
|
||||||
this.label,
|
this.label,
|
||||||
this.value,
|
this.value,
|
||||||
this.increasedValue,
|
this.increasedValue,
|
||||||
@ -760,6 +790,26 @@ class SemanticsProperties extends DiagnosticableTree {
|
|||||||
/// * [UpdateLiveRegionEvent], to trigger a polite announcement of a live region.
|
/// * [UpdateLiveRegionEvent], to trigger a polite announcement of a live region.
|
||||||
final bool liveRegion;
|
final bool liveRegion;
|
||||||
|
|
||||||
|
/// The maximum number of characters that can be entered into an editable
|
||||||
|
/// text field.
|
||||||
|
///
|
||||||
|
/// For the purpose of this function a character is defined as one Unicode
|
||||||
|
/// scalar value.
|
||||||
|
///
|
||||||
|
/// This should only be set when [textField] is true. Defaults to null,
|
||||||
|
/// which means no limit is imposed on the text field.
|
||||||
|
final int maxValueLength;
|
||||||
|
|
||||||
|
/// The current number of characters that have been entered into an editable
|
||||||
|
/// text field.
|
||||||
|
///
|
||||||
|
/// For the purpose of this function a character is defined as one Unicode
|
||||||
|
/// scalar value.
|
||||||
|
///
|
||||||
|
/// This should only be set when [textField] is true. Must be set when
|
||||||
|
/// [maxValueLength] is set.
|
||||||
|
final int currentValueLength;
|
||||||
|
|
||||||
/// Provides a textual description of the widget.
|
/// Provides a textual description of the widget.
|
||||||
///
|
///
|
||||||
/// If a label is provided, there must either by an ambient [Directionality]
|
/// If a label is provided, there must either by an ambient [Directionality]
|
||||||
@ -1512,6 +1562,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
_actionsAsBits != config._actionsAsBits ||
|
_actionsAsBits != config._actionsAsBits ||
|
||||||
indexInParent != config.indexInParent ||
|
indexInParent != config.indexInParent ||
|
||||||
platformViewId != config.platformViewId ||
|
platformViewId != config.platformViewId ||
|
||||||
|
_maxValueLength != config._maxValueLength ||
|
||||||
|
_currentValueLength != config._currentValueLength ||
|
||||||
_mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants;
|
_mergeAllDescendantsIntoThisNode != config.isMergingSemanticsOfDescendants;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1729,6 +1781,28 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
int get platformViewId => _platformViewId;
|
int get platformViewId => _platformViewId;
|
||||||
int _platformViewId;
|
int _platformViewId;
|
||||||
|
|
||||||
|
/// The maximum number of characters that can be entered into an editable
|
||||||
|
/// text field.
|
||||||
|
///
|
||||||
|
/// For the purpose of this function a character is defined as one Unicode
|
||||||
|
/// scalar value.
|
||||||
|
///
|
||||||
|
/// This should only be set when [SemanticsFlag.isTextField] is set. Defaults
|
||||||
|
/// to null, which means no limit is imposed on the text field.
|
||||||
|
int get maxValueLength => _maxValueLength;
|
||||||
|
int _maxValueLength;
|
||||||
|
|
||||||
|
/// The current number of characters that have been entered into an editable
|
||||||
|
/// text field.
|
||||||
|
///
|
||||||
|
/// For the purpose of this function a character is defined as one Unicode
|
||||||
|
/// scalar value.
|
||||||
|
///
|
||||||
|
/// This should only be set when [SemanticsFlag.isTextField] is set. Must be
|
||||||
|
/// set when [maxValueLength] is set.
|
||||||
|
int get currentValueLength => _currentValueLength;
|
||||||
|
int _currentValueLength;
|
||||||
|
|
||||||
bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);
|
bool _canPerformAction(SemanticsAction action) => _actions.containsKey(action);
|
||||||
|
|
||||||
static final SemanticsConfiguration _kEmptyConfig = SemanticsConfiguration();
|
static final SemanticsConfiguration _kEmptyConfig = SemanticsConfiguration();
|
||||||
@ -1779,6 +1853,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
_scrollIndex = config.scrollIndex;
|
_scrollIndex = config.scrollIndex;
|
||||||
indexInParent = config.indexInParent;
|
indexInParent = config.indexInParent;
|
||||||
_platformViewId = config._platformViewId;
|
_platformViewId = config._platformViewId;
|
||||||
|
_maxValueLength = config._maxValueLength;
|
||||||
|
_currentValueLength = config._currentValueLength;
|
||||||
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
|
_replaceChildren(childrenInInversePaintOrder ?? const <SemanticsNode>[]);
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
@ -1814,6 +1890,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
double scrollExtentMax = _scrollExtentMax;
|
double scrollExtentMax = _scrollExtentMax;
|
||||||
double scrollExtentMin = _scrollExtentMin;
|
double scrollExtentMin = _scrollExtentMin;
|
||||||
int platformViewId = _platformViewId;
|
int platformViewId = _platformViewId;
|
||||||
|
int maxValueLength = _maxValueLength;
|
||||||
|
int currentValueLength = _currentValueLength;
|
||||||
final double elevation = _elevation;
|
final double elevation = _elevation;
|
||||||
double thickness = _thickness;
|
double thickness = _thickness;
|
||||||
final Set<int> customSemanticsActionIds = <int>{};
|
final Set<int> customSemanticsActionIds = <int>{};
|
||||||
@ -1849,6 +1927,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
scrollExtentMax ??= node._scrollExtentMax;
|
scrollExtentMax ??= node._scrollExtentMax;
|
||||||
scrollExtentMin ??= node._scrollExtentMin;
|
scrollExtentMin ??= node._scrollExtentMin;
|
||||||
platformViewId ??= node._platformViewId;
|
platformViewId ??= node._platformViewId;
|
||||||
|
maxValueLength ??= node._maxValueLength;
|
||||||
|
currentValueLength ??= node._currentValueLength;
|
||||||
if (value == '' || value == null)
|
if (value == '' || value == null)
|
||||||
value = node._value;
|
value = node._value;
|
||||||
if (increasedValue == '' || increasedValue == null)
|
if (increasedValue == '' || increasedValue == null)
|
||||||
@ -1919,6 +1999,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
scrollExtentMax: scrollExtentMax,
|
scrollExtentMax: scrollExtentMax,
|
||||||
scrollExtentMin: scrollExtentMin,
|
scrollExtentMin: scrollExtentMin,
|
||||||
platformViewId: platformViewId,
|
platformViewId: platformViewId,
|
||||||
|
maxValueLength: maxValueLength,
|
||||||
|
currentValueLength: currentValueLength,
|
||||||
customSemanticsActionIds: customSemanticsActionIds.toList()..sort(),
|
customSemanticsActionIds: customSemanticsActionIds.toList()..sort(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1975,6 +2057,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
textSelectionBase: data.textSelection != null ? data.textSelection.baseOffset : -1,
|
textSelectionBase: data.textSelection != null ? data.textSelection.baseOffset : -1,
|
||||||
textSelectionExtent: data.textSelection != null ? data.textSelection.extentOffset : -1,
|
textSelectionExtent: data.textSelection != null ? data.textSelection.extentOffset : -1,
|
||||||
platformViewId: data.platformViewId ?? -1,
|
platformViewId: data.platformViewId ?? -1,
|
||||||
|
maxValueLength: data.maxValueLength ?? -1,
|
||||||
|
currentValueLength: data.currentValueLength ?? -1,
|
||||||
scrollChildren: data.scrollChildCount ?? 0,
|
scrollChildren: data.scrollChildCount ?? 0,
|
||||||
scrollIndex: data.scrollIndex ?? 0 ,
|
scrollIndex: data.scrollIndex ?? 0 ,
|
||||||
scrollPosition: data.scrollPosition ?? double.nan,
|
scrollPosition: data.scrollPosition ?? double.nan,
|
||||||
@ -2118,6 +2202,8 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
|
|||||||
if (_textSelection?.isValid == true)
|
if (_textSelection?.isValid == true)
|
||||||
properties.add(MessageProperty('text selection', '[${_textSelection.start}, ${_textSelection.end}]'));
|
properties.add(MessageProperty('text selection', '[${_textSelection.start}, ${_textSelection.end}]'));
|
||||||
properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
|
properties.add(IntProperty('platformViewId', platformViewId, defaultValue: null));
|
||||||
|
properties.add(IntProperty('maxValueLength', maxValueLength, defaultValue: null));
|
||||||
|
properties.add(IntProperty('currentValueLength', currentValueLength, defaultValue: null));
|
||||||
properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
|
properties.add(IntProperty('scrollChildren', scrollChildCount, defaultValue: null));
|
||||||
properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
|
properties.add(IntProperty('scrollIndex', scrollIndex, defaultValue: null));
|
||||||
properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
|
properties.add(DoubleProperty('scrollExtentMin', scrollExtentMin, defaultValue: null));
|
||||||
@ -3188,6 +3274,40 @@ class SemanticsConfiguration {
|
|||||||
_hasBeenAnnotated = true;
|
_hasBeenAnnotated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The maximum number of characters that can be entered into an editable
|
||||||
|
/// text field.
|
||||||
|
///
|
||||||
|
/// For the purpose of this function a character is defined as one Unicode
|
||||||
|
/// scalar value.
|
||||||
|
///
|
||||||
|
/// This should only be set when [isTextField] is true. Defaults to null,
|
||||||
|
/// which means no limit is imposed on the text field.
|
||||||
|
int get maxValueLength => _maxValueLength;
|
||||||
|
int _maxValueLength;
|
||||||
|
set maxValueLength(int value) {
|
||||||
|
if (value == maxValueLength)
|
||||||
|
return;
|
||||||
|
_maxValueLength = value;
|
||||||
|
_hasBeenAnnotated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The current number of characters that have been entered into an editable
|
||||||
|
/// text field.
|
||||||
|
///
|
||||||
|
/// For the purpose of this function a character is defined as one Unicode
|
||||||
|
/// scalar value.
|
||||||
|
///
|
||||||
|
/// This should only be set when [isTextField] is true. Must be set when
|
||||||
|
/// [maxValueLength] is set.
|
||||||
|
int get currentValueLength => _currentValueLength;
|
||||||
|
int _currentValueLength;
|
||||||
|
set currentValueLength(int value) {
|
||||||
|
if (value == currentValueLength)
|
||||||
|
return;
|
||||||
|
_currentValueLength = value;
|
||||||
|
_hasBeenAnnotated = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the semantic information provided by the owning [RenderObject] and
|
/// Whether the semantic information provided by the owning [RenderObject] and
|
||||||
/// all of its descendants should be treated as one logical entity.
|
/// all of its descendants should be treated as one logical entity.
|
||||||
///
|
///
|
||||||
@ -3690,6 +3810,12 @@ class SemanticsConfiguration {
|
|||||||
if (_platformViewId != null && other._platformViewId != null) {
|
if (_platformViewId != null && other._platformViewId != null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (_maxValueLength != null && other._maxValueLength != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_currentValueLength != null && other._currentValueLength != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (_value != null && _value.isNotEmpty && other._value != null && other._value.isNotEmpty)
|
if (_value != null && _value.isNotEmpty && other._value != null && other._value.isNotEmpty)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
@ -3725,6 +3851,8 @@ class SemanticsConfiguration {
|
|||||||
_scrollIndex ??= child._scrollIndex;
|
_scrollIndex ??= child._scrollIndex;
|
||||||
_scrollChildCount ??= child._scrollChildCount;
|
_scrollChildCount ??= child._scrollChildCount;
|
||||||
_platformViewId ??= child._platformViewId;
|
_platformViewId ??= child._platformViewId;
|
||||||
|
_maxValueLength ??= child._maxValueLength;
|
||||||
|
_currentValueLength ??= child._currentValueLength;
|
||||||
|
|
||||||
textDirection ??= child.textDirection;
|
textDirection ??= child.textDirection;
|
||||||
_sortKey ??= child._sortKey;
|
_sortKey ??= child._sortKey;
|
||||||
@ -3781,6 +3909,8 @@ class SemanticsConfiguration {
|
|||||||
.._scrollIndex = _scrollIndex
|
.._scrollIndex = _scrollIndex
|
||||||
.._scrollChildCount = _scrollChildCount
|
.._scrollChildCount = _scrollChildCount
|
||||||
.._platformViewId = _platformViewId
|
.._platformViewId = _platformViewId
|
||||||
|
.._maxValueLength = _maxValueLength
|
||||||
|
.._currentValueLength = _currentValueLength
|
||||||
.._actions.addAll(_actions)
|
.._actions.addAll(_actions)
|
||||||
.._customSemanticsActions.addAll(_customSemanticsActions);
|
.._customSemanticsActions.addAll(_customSemanticsActions);
|
||||||
}
|
}
|
||||||
|
@ -6196,6 +6196,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||||||
bool hidden,
|
bool hidden,
|
||||||
bool image,
|
bool image,
|
||||||
bool liveRegion,
|
bool liveRegion,
|
||||||
|
int maxValueLength,
|
||||||
|
int currentValueLength,
|
||||||
String label,
|
String label,
|
||||||
String value,
|
String value,
|
||||||
String increasedValue,
|
String increasedValue,
|
||||||
@ -6247,6 +6249,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||||||
hidden: hidden,
|
hidden: hidden,
|
||||||
image: image,
|
image: image,
|
||||||
liveRegion: liveRegion,
|
liveRegion: liveRegion,
|
||||||
|
maxValueLength: maxValueLength,
|
||||||
|
currentValueLength: currentValueLength,
|
||||||
label: label,
|
label: label,
|
||||||
value: value,
|
value: value,
|
||||||
increasedValue: increasedValue,
|
increasedValue: increasedValue,
|
||||||
@ -6350,6 +6354,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||||||
readOnly: properties.readOnly,
|
readOnly: properties.readOnly,
|
||||||
focused: properties.focused,
|
focused: properties.focused,
|
||||||
liveRegion: properties.liveRegion,
|
liveRegion: properties.liveRegion,
|
||||||
|
maxValueLength: properties.maxValueLength,
|
||||||
|
currentValueLength: properties.currentValueLength,
|
||||||
inMutuallyExclusiveGroup: properties.inMutuallyExclusiveGroup,
|
inMutuallyExclusiveGroup: properties.inMutuallyExclusiveGroup,
|
||||||
obscured: properties.obscured,
|
obscured: properties.obscured,
|
||||||
multiline: properties.multiline,
|
multiline: properties.multiline,
|
||||||
@ -6422,6 +6428,8 @@ class Semantics extends SingleChildRenderObjectWidget {
|
|||||||
..hidden = properties.hidden
|
..hidden = properties.hidden
|
||||||
..image = properties.image
|
..image = properties.image
|
||||||
..liveRegion = properties.liveRegion
|
..liveRegion = properties.liveRegion
|
||||||
|
..maxValueLength = properties.maxValueLength
|
||||||
|
..currentValueLength = properties.currentValueLength
|
||||||
..label = properties.label
|
..label = properties.label
|
||||||
..value = properties.value
|
..value = properties.value
|
||||||
..increasedValue = properties.increasedValue
|
..increasedValue = properties.increasedValue
|
||||||
|
@ -3367,6 +3367,68 @@ void main() {
|
|||||||
semantics.dispose();
|
semantics.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Disabled text field does not have tap action', (WidgetTester tester) async {
|
||||||
|
final SemanticsTester semantics = SemanticsTester(tester);
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: TextField(
|
||||||
|
maxLength: 10,
|
||||||
|
enabled: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(semantics, isNot(includesNodeWith(actions: <SemanticsAction>[SemanticsAction.tap])));
|
||||||
|
|
||||||
|
semantics.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('currentValueLength/maxValueLength are in the tree', (WidgetTester tester) async {
|
||||||
|
final SemanticsTester semantics = SemanticsTester(tester);
|
||||||
|
final TextEditingController controller = TextEditingController();
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Material(
|
||||||
|
child: Center(
|
||||||
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
|
maxLength: 10,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(semantics, includesNodeWith(
|
||||||
|
flags: <SemanticsFlag>[SemanticsFlag.isTextField],
|
||||||
|
maxValueLength: 10,
|
||||||
|
currentValueLength: 0,
|
||||||
|
));
|
||||||
|
|
||||||
|
await tester.showKeyboard(find.byType(TextField));
|
||||||
|
const String testValue = '123';
|
||||||
|
tester.testTextInput.updateEditingValue(const TextEditingValue(
|
||||||
|
text: testValue,
|
||||||
|
selection: TextSelection.collapsed(offset: 3),
|
||||||
|
composing: TextRange(start: 0, end: testValue.length),
|
||||||
|
));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(semantics, includesNodeWith(
|
||||||
|
flags: <SemanticsFlag>[SemanticsFlag.isTextField, SemanticsFlag.isFocused],
|
||||||
|
maxValueLength: 10,
|
||||||
|
currentValueLength: 3,
|
||||||
|
));
|
||||||
|
|
||||||
|
semantics.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('Read only TextField identifies as read only text field in semantics', (WidgetTester tester) async {
|
testWidgets('Read only TextField identifies as read only text field in semantics', (WidgetTester tester) async {
|
||||||
final SemanticsTester semantics = SemanticsTester(tester);
|
final SemanticsTester semantics = SemanticsTester(tester);
|
||||||
|
|
||||||
|
@ -447,6 +447,8 @@ void main() {
|
|||||||
' textDirection: null\n'
|
' textDirection: null\n'
|
||||||
' sortKey: null\n'
|
' sortKey: null\n'
|
||||||
' platformViewId: null\n'
|
' platformViewId: null\n'
|
||||||
|
' maxValueLength: null\n'
|
||||||
|
' currentValueLength: null\n'
|
||||||
' scrollChildren: null\n'
|
' scrollChildren: null\n'
|
||||||
' scrollIndex: null\n'
|
' scrollIndex: null\n'
|
||||||
' scrollExtentMin: null\n'
|
' scrollExtentMin: null\n'
|
||||||
@ -543,6 +545,8 @@ void main() {
|
|||||||
' textDirection: null\n'
|
' textDirection: null\n'
|
||||||
' sortKey: null\n'
|
' sortKey: null\n'
|
||||||
' platformViewId: null\n'
|
' platformViewId: null\n'
|
||||||
|
' maxValueLength: null\n'
|
||||||
|
' currentValueLength: null\n'
|
||||||
' scrollChildren: null\n'
|
' scrollChildren: null\n'
|
||||||
' scrollIndex: null\n'
|
' scrollIndex: null\n'
|
||||||
' scrollExtentMin: null\n'
|
' scrollExtentMin: null\n'
|
||||||
|
@ -452,8 +452,11 @@ void main() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final dynamic semanticsDebuggerPainter = _getSemanticsDebuggerPainter(debuggerKey: debugger, tester: tester);
|
||||||
|
final RenderObject renderTextfield = tester.renderObject(find.descendant(of: find.byKey(textField), matching: find.byType(Semantics)).first);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
_getMessageShownInSemanticsDebugger(widgetKey: textField, debuggerKey: debugger, tester: tester),
|
semanticsDebuggerPainter.getMessage(renderTextfield.debugSemantics),
|
||||||
'textfield',
|
'textfield',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -463,6 +466,14 @@ String _getMessageShownInSemanticsDebugger({
|
|||||||
@required Key widgetKey,
|
@required Key widgetKey,
|
||||||
@required Key debuggerKey,
|
@required Key debuggerKey,
|
||||||
@required WidgetTester tester,
|
@required WidgetTester tester,
|
||||||
|
}) {
|
||||||
|
final dynamic semanticsDebuggerPainter = _getSemanticsDebuggerPainter(debuggerKey: debuggerKey, tester: tester);
|
||||||
|
return semanticsDebuggerPainter.getMessage(tester.renderObject(find.byKey(widgetKey)).debugSemantics);
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamic _getSemanticsDebuggerPainter({
|
||||||
|
@required Key debuggerKey,
|
||||||
|
@required WidgetTester tester,
|
||||||
}) {
|
}) {
|
||||||
final CustomPaint customPaint = tester.widgetList(find.descendant(
|
final CustomPaint customPaint = tester.widgetList(find.descendant(
|
||||||
of: find.byKey(debuggerKey),
|
of: find.byKey(debuggerKey),
|
||||||
@ -470,5 +481,5 @@ String _getMessageShownInSemanticsDebugger({
|
|||||||
)).first;
|
)).first;
|
||||||
final dynamic semanticsDebuggerPainter = customPaint.foregroundPainter;
|
final dynamic semanticsDebuggerPainter = customPaint.foregroundPainter;
|
||||||
expect(semanticsDebuggerPainter.runtimeType.toString(), '_SemanticsDebuggerPainter');
|
expect(semanticsDebuggerPainter.runtimeType.toString(), '_SemanticsDebuggerPainter');
|
||||||
return semanticsDebuggerPainter.getMessage(tester.renderObject(find.byKey(widgetKey)).debugSemantics);
|
return semanticsDebuggerPainter;
|
||||||
}
|
}
|
||||||
|
@ -442,6 +442,8 @@ class SemanticsTester {
|
|||||||
double scrollPosition,
|
double scrollPosition,
|
||||||
double scrollExtentMax,
|
double scrollExtentMax,
|
||||||
double scrollExtentMin,
|
double scrollExtentMin,
|
||||||
|
int currentValueLength,
|
||||||
|
int maxValueLength,
|
||||||
SemanticsNode ancestor,
|
SemanticsNode ancestor,
|
||||||
}) {
|
}) {
|
||||||
bool checkNode(SemanticsNode node) {
|
bool checkNode(SemanticsNode node) {
|
||||||
@ -471,6 +473,12 @@ class SemanticsTester {
|
|||||||
return false;
|
return false;
|
||||||
if (scrollExtentMin != null && !nearEqual(node.scrollExtentMin, scrollExtentMin, 0.1))
|
if (scrollExtentMin != null && !nearEqual(node.scrollExtentMin, scrollExtentMin, 0.1))
|
||||||
return false;
|
return false;
|
||||||
|
if (currentValueLength != null && node.currentValueLength != currentValueLength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (maxValueLength != null && node.maxValueLength != maxValueLength) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -713,7 +721,19 @@ class _IncludesNodeWith extends Matcher {
|
|||||||
this.scrollPosition,
|
this.scrollPosition,
|
||||||
this.scrollExtentMax,
|
this.scrollExtentMax,
|
||||||
this.scrollExtentMin,
|
this.scrollExtentMin,
|
||||||
}) : assert(label != null || value != null || actions != null || flags != null || scrollPosition != null || scrollExtentMax != null || scrollExtentMin != null);
|
this.maxValueLength,
|
||||||
|
this.currentValueLength,
|
||||||
|
}) : assert(
|
||||||
|
label != null ||
|
||||||
|
value != null ||
|
||||||
|
actions != null ||
|
||||||
|
flags != null ||
|
||||||
|
scrollPosition != null ||
|
||||||
|
scrollExtentMax != null ||
|
||||||
|
scrollExtentMin != null ||
|
||||||
|
maxValueLength != null ||
|
||||||
|
currentValueLength != null
|
||||||
|
);
|
||||||
|
|
||||||
final String label;
|
final String label;
|
||||||
final String value;
|
final String value;
|
||||||
@ -724,6 +744,8 @@ class _IncludesNodeWith extends Matcher {
|
|||||||
final double scrollPosition;
|
final double scrollPosition;
|
||||||
final double scrollExtentMax;
|
final double scrollExtentMax;
|
||||||
final double scrollExtentMin;
|
final double scrollExtentMin;
|
||||||
|
final int currentValueLength;
|
||||||
|
final int maxValueLength;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
|
bool matches(covariant SemanticsTester item, Map<dynamic, dynamic> matchState) {
|
||||||
@ -737,6 +759,8 @@ class _IncludesNodeWith extends Matcher {
|
|||||||
scrollPosition: scrollPosition,
|
scrollPosition: scrollPosition,
|
||||||
scrollExtentMax: scrollExtentMax,
|
scrollExtentMax: scrollExtentMax,
|
||||||
scrollExtentMin: scrollExtentMin,
|
scrollExtentMin: scrollExtentMin,
|
||||||
|
currentValueLength: currentValueLength,
|
||||||
|
maxValueLength: maxValueLength,
|
||||||
).isNotEmpty;
|
).isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -761,6 +785,8 @@ class _IncludesNodeWith extends Matcher {
|
|||||||
if (scrollPosition != null) 'scrollPosition "$scrollPosition"',
|
if (scrollPosition != null) 'scrollPosition "$scrollPosition"',
|
||||||
if (scrollExtentMax != null) 'scrollExtentMax "$scrollExtentMax"',
|
if (scrollExtentMax != null) 'scrollExtentMax "$scrollExtentMax"',
|
||||||
if (scrollExtentMin != null) 'scrollExtentMin "$scrollExtentMin"',
|
if (scrollExtentMin != null) 'scrollExtentMin "$scrollExtentMin"',
|
||||||
|
if (currentValueLength != null) 'currentValueLength "$currentValueLength"',
|
||||||
|
if (maxValueLength != null) 'maxValueLength "$maxValueLength"',
|
||||||
];
|
];
|
||||||
return strings.join(', ');
|
return strings.join(', ');
|
||||||
}
|
}
|
||||||
@ -780,6 +806,8 @@ Matcher includesNodeWith({
|
|||||||
double scrollPosition,
|
double scrollPosition,
|
||||||
double scrollExtentMax,
|
double scrollExtentMax,
|
||||||
double scrollExtentMin,
|
double scrollExtentMin,
|
||||||
|
int maxValueLength,
|
||||||
|
int currentValueLength,
|
||||||
}) {
|
}) {
|
||||||
return _IncludesNodeWith(
|
return _IncludesNodeWith(
|
||||||
label: label,
|
label: label,
|
||||||
@ -791,5 +819,7 @@ Matcher includesNodeWith({
|
|||||||
scrollPosition: scrollPosition,
|
scrollPosition: scrollPosition,
|
||||||
scrollExtentMax: scrollExtentMax,
|
scrollExtentMax: scrollExtentMax,
|
||||||
scrollExtentMin: scrollExtentMin,
|
scrollExtentMin: scrollExtentMin,
|
||||||
|
maxValueLength: maxValueLength,
|
||||||
|
currentValueLength: currentValueLength,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -317,6 +317,7 @@ class Descendant extends SerializableFinder {
|
|||||||
@required this.of,
|
@required this.of,
|
||||||
@required this.matching,
|
@required this.matching,
|
||||||
this.matchRoot = false,
|
this.matchRoot = false,
|
||||||
|
this.firstMatchOnly = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The finder specifying the widget of which the descendant is to be found.
|
/// The finder specifying the widget of which the descendant is to be found.
|
||||||
@ -328,6 +329,9 @@ class Descendant extends SerializableFinder {
|
|||||||
/// Whether the widget matching [of] will be considered for a match.
|
/// Whether the widget matching [of] will be considered for a match.
|
||||||
final bool matchRoot;
|
final bool matchRoot;
|
||||||
|
|
||||||
|
/// If true then only the first descendant matching `matching` will be returned.
|
||||||
|
final bool firstMatchOnly;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get finderType => 'Descendant';
|
String get finderType => 'Descendant';
|
||||||
|
|
||||||
@ -338,6 +342,7 @@ class Descendant extends SerializableFinder {
|
|||||||
..addAll(matching.serialize().map((String key, String value) => MapEntry<String, String>('matching_$key', value)))
|
..addAll(matching.serialize().map((String key, String value) => MapEntry<String, String>('matching_$key', value)))
|
||||||
..addAll(<String, String>{
|
..addAll(<String, String>{
|
||||||
'matchRoot': matchRoot ? 'true' : 'false',
|
'matchRoot': matchRoot ? 'true' : 'false',
|
||||||
|
'firstMatchOnly': firstMatchOnly ? 'true' : 'false',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,6 +364,7 @@ class Descendant extends SerializableFinder {
|
|||||||
of: SerializableFinder.deserialize(of),
|
of: SerializableFinder.deserialize(of),
|
||||||
matching: SerializableFinder.deserialize(matching),
|
matching: SerializableFinder.deserialize(matching),
|
||||||
matchRoot: other['matchRoot'] == 'true',
|
matchRoot: other['matchRoot'] == 'true',
|
||||||
|
firstMatchOnly: other['firstMatchOnly'] == 'true',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -374,6 +380,7 @@ class Ancestor extends SerializableFinder {
|
|||||||
@required this.of,
|
@required this.of,
|
||||||
@required this.matching,
|
@required this.matching,
|
||||||
this.matchRoot = false,
|
this.matchRoot = false,
|
||||||
|
this.firstMatchOnly = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The finder specifying the widget of which the ancestor is to be found.
|
/// The finder specifying the widget of which the ancestor is to be found.
|
||||||
@ -385,6 +392,9 @@ class Ancestor extends SerializableFinder {
|
|||||||
/// Whether the widget matching [of] will be considered for a match.
|
/// Whether the widget matching [of] will be considered for a match.
|
||||||
final bool matchRoot;
|
final bool matchRoot;
|
||||||
|
|
||||||
|
/// If true then only the first ancestor matching `matching` will be returned.
|
||||||
|
final bool firstMatchOnly;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get finderType => 'Ancestor';
|
String get finderType => 'Ancestor';
|
||||||
|
|
||||||
@ -395,6 +405,7 @@ class Ancestor extends SerializableFinder {
|
|||||||
..addAll(matching.serialize().map((String key, String value) => MapEntry<String, String>('matching_$key', value)))
|
..addAll(matching.serialize().map((String key, String value) => MapEntry<String, String>('matching_$key', value)))
|
||||||
..addAll(<String, String>{
|
..addAll(<String, String>{
|
||||||
'matchRoot': matchRoot ? 'true' : 'false',
|
'matchRoot': matchRoot ? 'true' : 'false',
|
||||||
|
'firstMatchOnly': firstMatchOnly ? 'true' : 'false',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,6 +427,7 @@ class Ancestor extends SerializableFinder {
|
|||||||
of: SerializableFinder.deserialize(of),
|
of: SerializableFinder.deserialize(of),
|
||||||
matching: SerializableFinder.deserialize(matching),
|
matching: SerializableFinder.deserialize(matching),
|
||||||
matchRoot: other['matchRoot'] == 'true',
|
matchRoot: other['matchRoot'] == 'true',
|
||||||
|
firstMatchOnly: other['firstMatchOnly'] == 'true',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1199,22 +1199,30 @@ class CommonFinders {
|
|||||||
///
|
///
|
||||||
/// If the `matchRoot` argument is true then the widget specified by `of` will
|
/// If the `matchRoot` argument is true then the widget specified by `of` will
|
||||||
/// be considered for a match. The argument defaults to false.
|
/// be considered for a match. The argument defaults to false.
|
||||||
|
///
|
||||||
|
/// If `firstMatchOnly` is true then only the first ancestor matching
|
||||||
|
/// `matching` will be returned. Defaults to false.
|
||||||
SerializableFinder ancestor({
|
SerializableFinder ancestor({
|
||||||
@required SerializableFinder of,
|
@required SerializableFinder of,
|
||||||
@required SerializableFinder matching,
|
@required SerializableFinder matching,
|
||||||
bool matchRoot = false,
|
bool matchRoot = false,
|
||||||
}) => Ancestor(of: of, matching: matching, matchRoot: matchRoot);
|
bool firstMatchOnly = false,
|
||||||
|
}) => Ancestor(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
|
||||||
|
|
||||||
/// Finds the widget that is an descendant of the `of` parameter and that
|
/// Finds the widget that is an descendant of the `of` parameter and that
|
||||||
/// matches the `matching` parameter.
|
/// matches the `matching` parameter.
|
||||||
///
|
///
|
||||||
/// If the `matchRoot` argument is true then the widget specified by `of` will
|
/// If the `matchRoot` argument is true then the widget specified by `of` will
|
||||||
/// be considered for a match. The argument defaults to false.
|
/// be considered for a match. The argument defaults to false.
|
||||||
|
///
|
||||||
|
/// If `firstMatchOnly` is true then only the first descendant matching
|
||||||
|
/// `matching` will be returned. Defaults to false.
|
||||||
SerializableFinder descendant({
|
SerializableFinder descendant({
|
||||||
@required SerializableFinder of,
|
@required SerializableFinder of,
|
||||||
@required SerializableFinder matching,
|
@required SerializableFinder matching,
|
||||||
bool matchRoot = false,
|
bool matchRoot = false,
|
||||||
}) => Descendant(of: of, matching: matching, matchRoot: matchRoot);
|
bool firstMatchOnly = false,
|
||||||
|
}) => Descendant(of: of, matching: matching, matchRoot: matchRoot, firstMatchOnly: firstMatchOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An immutable 2D floating-point offset used by Flutter Driver.
|
/// An immutable 2D floating-point offset used by Flutter Driver.
|
||||||
|
@ -335,19 +335,21 @@ class FlutterDriverExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Finder _createAncestorFinder(Ancestor arguments) {
|
Finder _createAncestorFinder(Ancestor arguments) {
|
||||||
return find.ancestor(
|
final Finder finder = find.ancestor(
|
||||||
of: _createFinder(arguments.of),
|
of: _createFinder(arguments.of),
|
||||||
matching: _createFinder(arguments.matching),
|
matching: _createFinder(arguments.matching),
|
||||||
matchRoot: arguments.matchRoot,
|
matchRoot: arguments.matchRoot,
|
||||||
);
|
);
|
||||||
|
return arguments.firstMatchOnly ? finder.first : finder;
|
||||||
}
|
}
|
||||||
|
|
||||||
Finder _createDescendantFinder(Descendant arguments) {
|
Finder _createDescendantFinder(Descendant arguments) {
|
||||||
return find.descendant(
|
final Finder finder = find.descendant(
|
||||||
of: _createFinder(arguments.of),
|
of: _createFinder(arguments.of),
|
||||||
matching: _createFinder(arguments.matching),
|
matching: _createFinder(arguments.matching),
|
||||||
matchRoot: arguments.matchRoot,
|
matchRoot: arguments.matchRoot,
|
||||||
);
|
);
|
||||||
|
return arguments.firstMatchOnly ? finder.first : finder;
|
||||||
}
|
}
|
||||||
|
|
||||||
Finder _createFinder(SerializableFinder finder) {
|
Finder _createFinder(SerializableFinder finder) {
|
||||||
|
@ -573,6 +573,39 @@ void main() {
|
|||||||
expect(await result, null);
|
expect(await result, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('descendant finder firstMatchOnly', (WidgetTester tester) async {
|
||||||
|
flutterDriverLog.listen((LogRecord _) {}); // Silence logging.
|
||||||
|
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true);
|
||||||
|
|
||||||
|
Future<String> getDescendantText() async {
|
||||||
|
final Map<String, Object> arguments = GetText(Descendant(
|
||||||
|
of: ByValueKey('column'),
|
||||||
|
matching: const ByType('Text'),
|
||||||
|
firstMatchOnly: true,
|
||||||
|
), timeout: const Duration(seconds: 1)).serialize();
|
||||||
|
final Map<String, dynamic> result = await extension.call(arguments);
|
||||||
|
if (result['isError']) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return GetTextResult.fromJson(result['response']).text;
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Column(
|
||||||
|
key: const ValueKey<String>('column'),
|
||||||
|
children: const <Widget>[
|
||||||
|
Text('Hello1', key: ValueKey<String>('text1')),
|
||||||
|
Text('Hello2', key: ValueKey<String>('text2')),
|
||||||
|
Text('Hello3', key: ValueKey<String>('text3')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await getDescendantText(), 'Hello1');
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('ancestor finder', (WidgetTester tester) async {
|
testWidgets('ancestor finder', (WidgetTester tester) async {
|
||||||
flutterDriverLog.listen((LogRecord _) {}); // Silence logging.
|
flutterDriverLog.listen((LogRecord _) {}); // Silence logging.
|
||||||
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true);
|
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true);
|
||||||
@ -642,6 +675,54 @@ void main() {
|
|||||||
expect(await result, null);
|
expect(await result, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ancestor finder firstMatchOnly', (WidgetTester tester) async {
|
||||||
|
flutterDriverLog.listen((LogRecord _) {}); // Silence logging.
|
||||||
|
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true);
|
||||||
|
|
||||||
|
Future<Offset> getAncestorTopLeft() async {
|
||||||
|
final Map<String, Object> arguments = GetOffset(Ancestor(
|
||||||
|
of: ByValueKey('leaf'),
|
||||||
|
matching: const ByType('Container'),
|
||||||
|
firstMatchOnly: true,
|
||||||
|
), OffsetType.topLeft, timeout: const Duration(seconds: 1)).serialize();
|
||||||
|
final Map<String, dynamic> response = await extension.call(arguments);
|
||||||
|
if (response['isError']) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final GetOffsetResult result = GetOffsetResult.fromJson(response['response']);
|
||||||
|
return Offset(result.dx, result.dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Center(
|
||||||
|
child: Container(
|
||||||
|
height: 200,
|
||||||
|
width: 200,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
height: 100,
|
||||||
|
width: 100,
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
key: const ValueKey<String>('leaf'),
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
await getAncestorTopLeft(),
|
||||||
|
const Offset((800 - 100) / 2, (600 - 100) / 2),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('GetDiagnosticsTree', (WidgetTester tester) async {
|
testWidgets('GetDiagnosticsTree', (WidgetTester tester) async {
|
||||||
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true);
|
final FlutterDriverExtension extension = FlutterDriverExtension((String arg) async => '', true);
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ void main() {
|
|||||||
of: of,
|
of: of,
|
||||||
matching: matching,
|
matching: matching,
|
||||||
matchRoot: true,
|
matchRoot: true,
|
||||||
|
firstMatchOnly: true,
|
||||||
);
|
);
|
||||||
expect(a.serialize(), <String, String>{
|
expect(a.serialize(), <String, String>{
|
||||||
'finderType': 'Ancestor',
|
'finderType': 'Ancestor',
|
||||||
@ -23,7 +24,8 @@ void main() {
|
|||||||
'matching_finderType': 'ByValueKey',
|
'matching_finderType': 'ByValueKey',
|
||||||
'matching_keyValueString': 'hello',
|
'matching_keyValueString': 'hello',
|
||||||
'matching_keyValueType': 'String',
|
'matching_keyValueType': 'String',
|
||||||
'matchRoot': 'true'
|
'matchRoot': 'true',
|
||||||
|
'firstMatchOnly': 'true',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -35,13 +37,15 @@ void main() {
|
|||||||
'matching_finderType': 'ByValueKey',
|
'matching_finderType': 'ByValueKey',
|
||||||
'matching_keyValueString': 'hello',
|
'matching_keyValueString': 'hello',
|
||||||
'matching_keyValueType': 'String',
|
'matching_keyValueType': 'String',
|
||||||
'matchRoot': 'true'
|
'matchRoot': 'true',
|
||||||
|
'firstMatchOnly': 'true',
|
||||||
};
|
};
|
||||||
|
|
||||||
final Ancestor a = Ancestor.deserialize(serialized);
|
final Ancestor a = Ancestor.deserialize(serialized);
|
||||||
expect(a.of, isA<ByType>());
|
expect(a.of, isA<ByType>());
|
||||||
expect(a.matching, isA<ByValueKey>());
|
expect(a.matching, isA<ByValueKey>());
|
||||||
expect(a.matchRoot, isTrue);
|
expect(a.matchRoot, isTrue);
|
||||||
|
expect(a.firstMatchOnly, isTrue);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Descendant finder serialize', () {
|
test('Descendant finder serialize', () {
|
||||||
@ -52,6 +56,7 @@ void main() {
|
|||||||
of: of,
|
of: of,
|
||||||
matching: matching,
|
matching: matching,
|
||||||
matchRoot: true,
|
matchRoot: true,
|
||||||
|
firstMatchOnly: true,
|
||||||
);
|
);
|
||||||
expect(a.serialize(), <String, String>{
|
expect(a.serialize(), <String, String>{
|
||||||
'finderType': 'Descendant',
|
'finderType': 'Descendant',
|
||||||
@ -60,7 +65,8 @@ void main() {
|
|||||||
'matching_finderType': 'ByValueKey',
|
'matching_finderType': 'ByValueKey',
|
||||||
'matching_keyValueString': 'hello',
|
'matching_keyValueString': 'hello',
|
||||||
'matching_keyValueType': 'String',
|
'matching_keyValueType': 'String',
|
||||||
'matchRoot': 'true'
|
'matchRoot': 'true',
|
||||||
|
'firstMatchOnly': 'true',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,12 +78,14 @@ void main() {
|
|||||||
'matching_finderType': 'ByValueKey',
|
'matching_finderType': 'ByValueKey',
|
||||||
'matching_keyValueString': 'hello',
|
'matching_keyValueString': 'hello',
|
||||||
'matching_keyValueType': 'String',
|
'matching_keyValueType': 'String',
|
||||||
'matchRoot': 'true'
|
'matchRoot': 'true',
|
||||||
|
'firstMatchOnly': 'true',
|
||||||
};
|
};
|
||||||
|
|
||||||
final Descendant a = Descendant.deserialize(serialized);
|
final Descendant a = Descendant.deserialize(serialized);
|
||||||
expect(a.of, isA<ByType>());
|
expect(a.of, isA<ByType>());
|
||||||
expect(a.matching, isA<ByValueKey>());
|
expect(a.matching, isA<ByValueKey>());
|
||||||
expect(a.matchRoot, isTrue);
|
expect(a.matchRoot, isTrue);
|
||||||
|
expect(a.firstMatchOnly, isTrue);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -433,6 +433,8 @@ Matcher matchesSemantics({
|
|||||||
double elevation,
|
double elevation,
|
||||||
double thickness,
|
double thickness,
|
||||||
int platformViewId,
|
int platformViewId,
|
||||||
|
int maxValueLength,
|
||||||
|
int currentValueLength,
|
||||||
// Flags //
|
// Flags //
|
||||||
bool hasCheckedState = false,
|
bool hasCheckedState = false,
|
||||||
bool isChecked = false,
|
bool isChecked = false,
|
||||||
@ -552,6 +554,8 @@ Matcher matchesSemantics({
|
|||||||
platformViewId: platformViewId,
|
platformViewId: platformViewId,
|
||||||
customActions: customActions,
|
customActions: customActions,
|
||||||
hintOverrides: hintOverrides,
|
hintOverrides: hintOverrides,
|
||||||
|
currentValueLength: currentValueLength,
|
||||||
|
maxValueLength: maxValueLength,
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1745,6 +1749,8 @@ class _MatchesSemanticsData extends Matcher {
|
|||||||
this.elevation,
|
this.elevation,
|
||||||
this.thickness,
|
this.thickness,
|
||||||
this.platformViewId,
|
this.platformViewId,
|
||||||
|
this.maxValueLength,
|
||||||
|
this.currentValueLength,
|
||||||
this.customActions,
|
this.customActions,
|
||||||
this.hintOverrides,
|
this.hintOverrides,
|
||||||
this.children,
|
this.children,
|
||||||
@ -1765,6 +1771,8 @@ class _MatchesSemanticsData extends Matcher {
|
|||||||
final double elevation;
|
final double elevation;
|
||||||
final double thickness;
|
final double thickness;
|
||||||
final int platformViewId;
|
final int platformViewId;
|
||||||
|
final int maxValueLength;
|
||||||
|
final int currentValueLength;
|
||||||
final List<Matcher> children;
|
final List<Matcher> children;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -1796,6 +1804,10 @@ class _MatchesSemanticsData extends Matcher {
|
|||||||
description.add(' with thickness: $thickness');
|
description.add(' with thickness: $thickness');
|
||||||
if (platformViewId != null)
|
if (platformViewId != null)
|
||||||
description.add(' with platformViewId: $platformViewId');
|
description.add(' with platformViewId: $platformViewId');
|
||||||
|
if (maxValueLength != null)
|
||||||
|
description.add(' with maxValueLength: $maxValueLength');
|
||||||
|
if (currentValueLength != null)
|
||||||
|
description.add(' with currentValueLength: $currentValueLength');
|
||||||
if (customActions != null)
|
if (customActions != null)
|
||||||
description.add(' with custom actions: $customActions');
|
description.add(' with custom actions: $customActions');
|
||||||
if (hintOverrides != null)
|
if (hintOverrides != null)
|
||||||
@ -1838,6 +1850,10 @@ class _MatchesSemanticsData extends Matcher {
|
|||||||
return failWithDescription(matchState, 'thickness was: ${data.thickness}');
|
return failWithDescription(matchState, 'thickness was: ${data.thickness}');
|
||||||
if (platformViewId != null && platformViewId != data.platformViewId)
|
if (platformViewId != null && platformViewId != data.platformViewId)
|
||||||
return failWithDescription(matchState, 'platformViewId was: ${data.platformViewId}');
|
return failWithDescription(matchState, 'platformViewId was: ${data.platformViewId}');
|
||||||
|
if (currentValueLength != null && currentValueLength != data.currentValueLength)
|
||||||
|
return failWithDescription(matchState, 'currentValueLength was: ${data.currentValueLength}');
|
||||||
|
if (maxValueLength != null && maxValueLength != data.maxValueLength)
|
||||||
|
return failWithDescription(matchState, 'maxValueLength was: ${data.maxValueLength}');
|
||||||
if (actions != null) {
|
if (actions != null) {
|
||||||
int actionBits = 0;
|
int actionBits = 0;
|
||||||
for (SemanticsAction action in actions)
|
for (SemanticsAction action in actions)
|
||||||
|
@ -525,6 +525,8 @@ void main() {
|
|||||||
scrollExtentMin: null,
|
scrollExtentMin: null,
|
||||||
platformViewId: 105,
|
platformViewId: 105,
|
||||||
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
|
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
|
||||||
|
currentValueLength: 10,
|
||||||
|
maxValueLength: 15,
|
||||||
);
|
);
|
||||||
final _FakeSemanticsNode node = _FakeSemanticsNode();
|
final _FakeSemanticsNode node = _FakeSemanticsNode();
|
||||||
node.data = data;
|
node.data = data;
|
||||||
@ -535,6 +537,8 @@ void main() {
|
|||||||
elevation: 3.0,
|
elevation: 3.0,
|
||||||
thickness: 4.0,
|
thickness: 4.0,
|
||||||
platformViewId: 105,
|
platformViewId: 105,
|
||||||
|
currentValueLength: 10,
|
||||||
|
maxValueLength: 15,
|
||||||
/* Flags */
|
/* Flags */
|
||||||
hasCheckedState: true,
|
hasCheckedState: true,
|
||||||
isChecked: true,
|
isChecked: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user