Unverified Commit e4995301 authored by pdblasi-google's avatar pdblasi-google Committed by GitHub

Create `containsSemantics` to allow for partial matching of semantics in tests. (#108573)

parent e368e586
...@@ -2315,7 +2315,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -2315,7 +2315,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
); );
assert( assert(
!_canPerformAction(SemanticsAction.decrease) || (value == '') == (decreasedValue == ''), !_canPerformAction(SemanticsAction.decrease) || (value == '') == (decreasedValue == ''),
'A SemanticsNode with action "increase" needs to be annotated with either both "value" and "decreasedValue" or neither', 'A SemanticsNode with action "decrease" needs to be annotated with either both "value" and "decreasedValue" or neither',
); );
} }
......
...@@ -514,6 +514,7 @@ AsyncMatcher matchesReferenceImage(ui.Image image) { ...@@ -514,6 +514,7 @@ AsyncMatcher matchesReferenceImage(ui.Image image) {
/// See also: /// See also:
/// ///
/// * [WidgetTester.getSemantics], the tester method which retrieves semantics. /// * [WidgetTester.getSemantics], the tester method which retrieves semantics.
/// * [containsSemantics], a similar matcher without default values for flags or actions.
Matcher matchesSemantics({ Matcher matchesSemantics({
String? label, String? label,
AttributedString? attributedLabel, AttributedString? attributedLabel,
...@@ -524,8 +525,8 @@ Matcher matchesSemantics({ ...@@ -524,8 +525,8 @@ Matcher matchesSemantics({
String? increasedValue, String? increasedValue,
AttributedString? attributedIncreasedValue, AttributedString? attributedIncreasedValue,
String? decreasedValue, String? decreasedValue,
String? tooltip,
AttributedString? attributedDecreasedValue, AttributedString? attributedDecreasedValue,
String? tooltip,
TextDirection? textDirection, TextDirection? textDirection,
Rect? rect, Rect? rect,
Size? size, Size? size,
...@@ -588,67 +589,6 @@ Matcher matchesSemantics({ ...@@ -588,67 +589,6 @@ Matcher matchesSemantics({
List<CustomSemanticsAction>? customActions, List<CustomSemanticsAction>? customActions,
List<Matcher>? children, List<Matcher>? children,
}) { }) {
final List<SemanticsFlag> flags = <SemanticsFlag>[
if (hasCheckedState) SemanticsFlag.hasCheckedState,
if (isChecked) SemanticsFlag.isChecked,
if (isSelected) SemanticsFlag.isSelected,
if (isButton) SemanticsFlag.isButton,
if (isSlider) SemanticsFlag.isSlider,
if (isKeyboardKey) SemanticsFlag.isKeyboardKey,
if (isLink) SemanticsFlag.isLink,
if (isTextField) SemanticsFlag.isTextField,
if (isReadOnly) SemanticsFlag.isReadOnly,
if (isFocused) SemanticsFlag.isFocused,
if (isFocusable) SemanticsFlag.isFocusable,
if (hasEnabledState) SemanticsFlag.hasEnabledState,
if (isEnabled) SemanticsFlag.isEnabled,
if (isInMutuallyExclusiveGroup) SemanticsFlag.isInMutuallyExclusiveGroup,
if (isHeader) SemanticsFlag.isHeader,
if (isObscured) SemanticsFlag.isObscured,
if (isMultiline) SemanticsFlag.isMultiline,
if (namesRoute) SemanticsFlag.namesRoute,
if (scopesRoute) SemanticsFlag.scopesRoute,
if (isHidden) SemanticsFlag.isHidden,
if (isImage) SemanticsFlag.isImage,
if (isLiveRegion) SemanticsFlag.isLiveRegion,
if (hasToggledState) SemanticsFlag.hasToggledState,
if (isToggled) SemanticsFlag.isToggled,
if (hasImplicitScrolling) SemanticsFlag.hasImplicitScrolling,
if (isSlider) SemanticsFlag.isSlider,
];
final List<SemanticsAction> actions = <SemanticsAction>[
if (hasTapAction) SemanticsAction.tap,
if (hasLongPressAction) SemanticsAction.longPress,
if (hasScrollLeftAction) SemanticsAction.scrollLeft,
if (hasScrollRightAction) SemanticsAction.scrollRight,
if (hasScrollUpAction) SemanticsAction.scrollUp,
if (hasScrollDownAction) SemanticsAction.scrollDown,
if (hasIncreaseAction) SemanticsAction.increase,
if (hasDecreaseAction) SemanticsAction.decrease,
if (hasShowOnScreenAction) SemanticsAction.showOnScreen,
if (hasMoveCursorForwardByCharacterAction) SemanticsAction.moveCursorForwardByCharacter,
if (hasMoveCursorBackwardByCharacterAction) SemanticsAction.moveCursorBackwardByCharacter,
if (hasSetSelectionAction) SemanticsAction.setSelection,
if (hasCopyAction) SemanticsAction.copy,
if (hasCutAction) SemanticsAction.cut,
if (hasPasteAction) SemanticsAction.paste,
if (hasDidGainAccessibilityFocusAction) SemanticsAction.didGainAccessibilityFocus,
if (hasDidLoseAccessibilityFocusAction) SemanticsAction.didLoseAccessibilityFocus,
if (customActions != null && customActions.isNotEmpty) SemanticsAction.customAction,
if (hasDismissAction) SemanticsAction.dismiss,
if (hasMoveCursorForwardByWordAction) SemanticsAction.moveCursorForwardByWord,
if (hasMoveCursorBackwardByWordAction) SemanticsAction.moveCursorBackwardByWord,
if (hasSetTextAction) SemanticsAction.setText,
];
SemanticsHintOverrides? hintOverrides;
if (onTapHint != null || onLongPressHint != null) {
hintOverrides = SemanticsHintOverrides(
onTapHint: onTapHint,
onLongPressHint: onLongPressHint,
);
}
return _MatchesSemanticsData( return _MatchesSemanticsData(
label: label, label: label,
attributedLabel: attributedLabel, attributedLabel: attributedLabel,
...@@ -657,12 +597,10 @@ Matcher matchesSemantics({ ...@@ -657,12 +597,10 @@ Matcher matchesSemantics({
value: value, value: value,
attributedValue: attributedValue, attributedValue: attributedValue,
increasedValue: increasedValue, increasedValue: increasedValue,
tooltip: tooltip,
attributedIncreasedValue: attributedIncreasedValue, attributedIncreasedValue: attributedIncreasedValue,
decreasedValue: decreasedValue, decreasedValue: decreasedValue,
attributedDecreasedValue: attributedDecreasedValue, attributedDecreasedValue: attributedDecreasedValue,
actions: actions, tooltip: tooltip,
flags: flags,
textDirection: textDirection, textDirection: textDirection,
rect: rect, rect: rect,
size: size, size: size,
...@@ -670,10 +608,231 @@ Matcher matchesSemantics({ ...@@ -670,10 +608,231 @@ Matcher matchesSemantics({
thickness: thickness, thickness: thickness,
platformViewId: platformViewId, platformViewId: platformViewId,
customActions: customActions, customActions: customActions,
hintOverrides: hintOverrides, maxValueLength: maxValueLength,
currentValueLength: currentValueLength, currentValueLength: currentValueLength,
// Flags
hasCheckedState: hasCheckedState,
isChecked: isChecked,
isSelected: isSelected,
isButton: isButton,
isSlider: isSlider,
isKeyboardKey: isKeyboardKey,
isLink: isLink,
isFocused: isFocused,
isFocusable: isFocusable,
isTextField: isTextField,
isReadOnly: isReadOnly,
hasEnabledState: hasEnabledState,
isEnabled: isEnabled,
isInMutuallyExclusiveGroup: isInMutuallyExclusiveGroup,
isHeader: isHeader,
isObscured: isObscured,
isMultiline: isMultiline,
namesRoute: namesRoute,
scopesRoute: scopesRoute,
isHidden: isHidden,
isImage: isImage,
isLiveRegion: isLiveRegion,
hasToggledState: hasToggledState,
isToggled: isToggled,
hasImplicitScrolling: hasImplicitScrolling,
// Actions
hasTapAction: hasTapAction,
hasLongPressAction: hasLongPressAction,
hasScrollLeftAction: hasScrollLeftAction,
hasScrollRightAction: hasScrollRightAction,
hasScrollUpAction: hasScrollUpAction,
hasScrollDownAction: hasScrollDownAction,
hasIncreaseAction: hasIncreaseAction,
hasDecreaseAction: hasDecreaseAction,
hasShowOnScreenAction: hasShowOnScreenAction,
hasMoveCursorForwardByCharacterAction: hasMoveCursorForwardByCharacterAction,
hasMoveCursorBackwardByCharacterAction: hasMoveCursorBackwardByCharacterAction,
hasMoveCursorForwardByWordAction: hasMoveCursorForwardByWordAction,
hasMoveCursorBackwardByWordAction: hasMoveCursorBackwardByWordAction,
hasSetTextAction: hasSetTextAction,
hasSetSelectionAction: hasSetSelectionAction,
hasCopyAction: hasCopyAction,
hasCutAction: hasCutAction,
hasPasteAction: hasPasteAction,
hasDidGainAccessibilityFocusAction: hasDidGainAccessibilityFocusAction,
hasDidLoseAccessibilityFocusAction: hasDidLoseAccessibilityFocusAction,
hasDismissAction: hasDismissAction,
// Custom actions and overrides
children: children,
onLongPressHint: onLongPressHint,
onTapHint: onTapHint,
);
}
/// Asserts that a [SemanticsNode] contains the specified information.
///
/// There are no default expected values, so no unspecified values will be
/// validated.
///
/// To retrieve the semantics data of a widget, use [WidgetTester.getSemantics]
/// with a [Finder] that returns a single widget. Semantics must be enabled
/// in order to use this method.
///
/// ## Sample code
///
/// ```dart
/// final SemanticsHandle handle = tester.ensureSemantics();
/// expect(tester.getSemantics(find.text('hello')), hasSemantics(label: 'hello'));
/// handle.dispose();
/// ```
///
/// See also:
///
/// * [WidgetTester.getSemantics], the tester method which retrieves semantics.
/// * [matchesSemantics], a similar matcher with default values for flags and actions.
Matcher containsSemantics({
String? label,
AttributedString? attributedLabel,
String? hint,
AttributedString? attributedHint,
String? value,
AttributedString? attributedValue,
String? increasedValue,
AttributedString? attributedIncreasedValue,
String? decreasedValue,
AttributedString? attributedDecreasedValue,
String? tooltip,
TextDirection? textDirection,
Rect? rect,
Size? size,
double? elevation,
double? thickness,
int? platformViewId,
int? maxValueLength,
int? currentValueLength,
// Flags
bool? hasCheckedState,
bool? isChecked,
bool? isSelected,
bool? isButton,
bool? isSlider,
bool? isKeyboardKey,
bool? isLink,
bool? isFocused,
bool? isFocusable,
bool? isTextField,
bool? isReadOnly,
bool? hasEnabledState,
bool? isEnabled,
bool? isInMutuallyExclusiveGroup,
bool? isHeader,
bool? isObscured,
bool? isMultiline,
bool? namesRoute,
bool? scopesRoute,
bool? isHidden,
bool? isImage,
bool? isLiveRegion,
bool? hasToggledState,
bool? isToggled,
bool? hasImplicitScrolling,
// Actions
bool? hasTapAction,
bool? hasLongPressAction,
bool? hasScrollLeftAction,
bool? hasScrollRightAction,
bool? hasScrollUpAction,
bool? hasScrollDownAction,
bool? hasIncreaseAction,
bool? hasDecreaseAction,
bool? hasShowOnScreenAction,
bool? hasMoveCursorForwardByCharacterAction,
bool? hasMoveCursorBackwardByCharacterAction,
bool? hasMoveCursorForwardByWordAction,
bool? hasMoveCursorBackwardByWordAction,
bool? hasSetTextAction,
bool? hasSetSelectionAction,
bool? hasCopyAction,
bool? hasCutAction,
bool? hasPasteAction,
bool? hasDidGainAccessibilityFocusAction,
bool? hasDidLoseAccessibilityFocusAction,
bool? hasDismissAction,
// Custom actions and overrides
String? onTapHint,
String? onLongPressHint,
List<CustomSemanticsAction>? customActions,
List<Matcher>? children,
}) {
return _MatchesSemanticsData(
label: label,
attributedLabel: attributedLabel,
hint: hint,
attributedHint: attributedHint,
value: value,
attributedValue: attributedValue,
increasedValue: increasedValue,
attributedIncreasedValue: attributedIncreasedValue,
decreasedValue: decreasedValue,
attributedDecreasedValue: attributedDecreasedValue,
tooltip: tooltip,
textDirection: textDirection,
rect: rect,
size: size,
elevation: elevation,
thickness: thickness,
platformViewId: platformViewId,
customActions: customActions,
maxValueLength: maxValueLength, maxValueLength: maxValueLength,
currentValueLength: currentValueLength,
// Flags
hasCheckedState: hasCheckedState,
isChecked: isChecked,
isSelected: isSelected,
isButton: isButton,
isSlider: isSlider,
isKeyboardKey: isKeyboardKey,
isLink: isLink,
isFocused: isFocused,
isFocusable: isFocusable,
isTextField: isTextField,
isReadOnly: isReadOnly,
hasEnabledState: hasEnabledState,
isEnabled: isEnabled,
isInMutuallyExclusiveGroup: isInMutuallyExclusiveGroup,
isHeader: isHeader,
isObscured: isObscured,
isMultiline: isMultiline,
namesRoute: namesRoute,
scopesRoute: scopesRoute,
isHidden: isHidden,
isImage: isImage,
isLiveRegion: isLiveRegion,
hasToggledState: hasToggledState,
isToggled: isToggled,
hasImplicitScrolling: hasImplicitScrolling,
// Actions
hasTapAction: hasTapAction,
hasLongPressAction: hasLongPressAction,
hasScrollLeftAction: hasScrollLeftAction,
hasScrollRightAction: hasScrollRightAction,
hasScrollUpAction: hasScrollUpAction,
hasScrollDownAction: hasScrollDownAction,
hasIncreaseAction: hasIncreaseAction,
hasDecreaseAction: hasDecreaseAction,
hasShowOnScreenAction: hasShowOnScreenAction,
hasMoveCursorForwardByCharacterAction: hasMoveCursorForwardByCharacterAction,
hasMoveCursorBackwardByCharacterAction: hasMoveCursorBackwardByCharacterAction,
hasMoveCursorForwardByWordAction: hasMoveCursorForwardByWordAction,
hasMoveCursorBackwardByWordAction: hasMoveCursorBackwardByWordAction,
hasSetTextAction: hasSetTextAction,
hasSetSelectionAction: hasSetSelectionAction,
hasCopyAction: hasCopyAction,
hasCutAction: hasCutAction,
hasPasteAction: hasPasteAction,
hasDidGainAccessibilityFocusAction: hasDidGainAccessibilityFocusAction,
hasDidLoseAccessibilityFocusAction: hasDidLoseAccessibilityFocusAction,
hasDismissAction: hasDismissAction,
// Custom actions and overrides
children: children, children: children,
onLongPressHint: onLongPressHint,
onTapHint: onTapHint,
); );
} }
...@@ -1885,31 +2044,136 @@ class _MatchesReferenceImage extends AsyncMatcher { ...@@ -1885,31 +2044,136 @@ class _MatchesReferenceImage extends AsyncMatcher {
class _MatchesSemanticsData extends Matcher { class _MatchesSemanticsData extends Matcher {
_MatchesSemanticsData({ _MatchesSemanticsData({
this.label, required this.label,
this.attributedLabel, required this.attributedLabel,
this.hint, required this.hint,
this.attributedHint, required this.attributedHint,
this.value, required this.value,
this.attributedValue, required this.attributedValue,
this.increasedValue, required this.increasedValue,
this.attributedIncreasedValue, required this.attributedIncreasedValue,
this.decreasedValue, required this.decreasedValue,
this.attributedDecreasedValue, required this.attributedDecreasedValue,
this.tooltip, required this.tooltip,
this.flags, required this.textDirection,
this.actions, required this.rect,
this.textDirection, required this.size,
this.rect, required this.elevation,
this.size, required this.thickness,
this.elevation, required this.platformViewId,
this.thickness, required this.maxValueLength,
this.platformViewId, required this.currentValueLength,
this.maxValueLength, // Flags
this.currentValueLength, required bool? hasCheckedState,
this.customActions, required bool? isChecked,
this.hintOverrides, required bool? isSelected,
this.children, required bool? isButton,
}); required bool? isSlider,
required bool? isKeyboardKey,
required bool? isLink,
required bool? isFocused,
required bool? isFocusable,
required bool? isTextField,
required bool? isReadOnly,
required bool? hasEnabledState,
required bool? isEnabled,
required bool? isInMutuallyExclusiveGroup,
required bool? isHeader,
required bool? isObscured,
required bool? isMultiline,
required bool? namesRoute,
required bool? scopesRoute,
required bool? isHidden,
required bool? isImage,
required bool? isLiveRegion,
required bool? hasToggledState,
required bool? isToggled,
required bool? hasImplicitScrolling,
// Actions
required bool? hasTapAction,
required bool? hasLongPressAction,
required bool? hasScrollLeftAction,
required bool? hasScrollRightAction,
required bool? hasScrollUpAction,
required bool? hasScrollDownAction,
required bool? hasIncreaseAction,
required bool? hasDecreaseAction,
required bool? hasShowOnScreenAction,
required bool? hasMoveCursorForwardByCharacterAction,
required bool? hasMoveCursorBackwardByCharacterAction,
required bool? hasMoveCursorForwardByWordAction,
required bool? hasMoveCursorBackwardByWordAction,
required bool? hasSetTextAction,
required bool? hasSetSelectionAction,
required bool? hasCopyAction,
required bool? hasCutAction,
required bool? hasPasteAction,
required bool? hasDidGainAccessibilityFocusAction,
required bool? hasDidLoseAccessibilityFocusAction,
required bool? hasDismissAction,
// Custom actions and overrides
required String? onTapHint,
required String? onLongPressHint,
required this.customActions,
required this.children,
}) : flags = <SemanticsFlag, bool>{
if (hasCheckedState != null) SemanticsFlag.hasCheckedState: hasCheckedState,
if (isChecked != null) SemanticsFlag.isChecked: isChecked,
if (isSelected != null) SemanticsFlag.isSelected: isSelected,
if (isButton != null) SemanticsFlag.isButton: isButton,
if (isSlider != null) SemanticsFlag.isSlider: isSlider,
if (isKeyboardKey != null) SemanticsFlag.isKeyboardKey: isKeyboardKey,
if (isLink != null) SemanticsFlag.isLink: isLink,
if (isTextField != null) SemanticsFlag.isTextField: isTextField,
if (isReadOnly != null) SemanticsFlag.isReadOnly: isReadOnly,
if (isFocused != null) SemanticsFlag.isFocused: isFocused,
if (isFocusable != null) SemanticsFlag.isFocusable: isFocusable,
if (hasEnabledState != null) SemanticsFlag.hasEnabledState: hasEnabledState,
if (isEnabled != null) SemanticsFlag.isEnabled: isEnabled,
if (isInMutuallyExclusiveGroup != null) SemanticsFlag.isInMutuallyExclusiveGroup: isInMutuallyExclusiveGroup,
if (isHeader != null) SemanticsFlag.isHeader: isHeader,
if (isObscured != null) SemanticsFlag.isObscured: isObscured,
if (isMultiline != null) SemanticsFlag.isMultiline: isMultiline,
if (namesRoute != null) SemanticsFlag.namesRoute: namesRoute,
if (scopesRoute != null) SemanticsFlag.scopesRoute: scopesRoute,
if (isHidden != null) SemanticsFlag.isHidden: isHidden,
if (isImage != null) SemanticsFlag.isImage: isImage,
if (isLiveRegion != null) SemanticsFlag.isLiveRegion: isLiveRegion,
if (hasToggledState != null) SemanticsFlag.hasToggledState: hasToggledState,
if (isToggled != null) SemanticsFlag.isToggled: isToggled,
if (hasImplicitScrolling != null) SemanticsFlag.hasImplicitScrolling: hasImplicitScrolling,
if (isSlider != null) SemanticsFlag.isSlider: isSlider,
},
actions = <SemanticsAction, bool>{
if (hasTapAction != null) SemanticsAction.tap: hasTapAction,
if (hasLongPressAction != null) SemanticsAction.longPress: hasLongPressAction,
if (hasScrollLeftAction != null) SemanticsAction.scrollLeft: hasScrollLeftAction,
if (hasScrollRightAction != null) SemanticsAction.scrollRight: hasScrollRightAction,
if (hasScrollUpAction != null) SemanticsAction.scrollUp: hasScrollUpAction,
if (hasScrollDownAction != null) SemanticsAction.scrollDown: hasScrollDownAction,
if (hasIncreaseAction != null) SemanticsAction.increase: hasIncreaseAction,
if (hasDecreaseAction != null) SemanticsAction.decrease: hasDecreaseAction,
if (hasShowOnScreenAction != null) SemanticsAction.showOnScreen: hasShowOnScreenAction,
if (hasMoveCursorForwardByCharacterAction != null) SemanticsAction.moveCursorForwardByCharacter: hasMoveCursorForwardByCharacterAction,
if (hasMoveCursorBackwardByCharacterAction != null) SemanticsAction.moveCursorBackwardByCharacter: hasMoveCursorBackwardByCharacterAction,
if (hasSetSelectionAction != null) SemanticsAction.setSelection: hasSetSelectionAction,
if (hasCopyAction != null) SemanticsAction.copy: hasCopyAction,
if (hasCutAction != null) SemanticsAction.cut: hasCutAction,
if (hasPasteAction != null) SemanticsAction.paste: hasPasteAction,
if (hasDidGainAccessibilityFocusAction != null) SemanticsAction.didGainAccessibilityFocus: hasDidGainAccessibilityFocusAction,
if (hasDidLoseAccessibilityFocusAction != null) SemanticsAction.didLoseAccessibilityFocus: hasDidLoseAccessibilityFocusAction,
if (customActions != null) SemanticsAction.customAction: customActions.isNotEmpty,
if (hasDismissAction != null) SemanticsAction.dismiss: hasDismissAction,
if (hasMoveCursorForwardByWordAction != null) SemanticsAction.moveCursorForwardByWord: hasMoveCursorForwardByWordAction,
if (hasMoveCursorBackwardByWordAction != null) SemanticsAction.moveCursorBackwardByWord: hasMoveCursorBackwardByWordAction,
if (hasSetTextAction != null) SemanticsAction.setText: hasSetTextAction,
},
hintOverrides = onTapHint == null && onLongPressHint == null
? null
: SemanticsHintOverrides(
onTapHint: onTapHint,
onLongPressHint: onLongPressHint,
);
final String? label; final String? label;
final AttributedString? attributedLabel; final AttributedString? attributedLabel;
...@@ -1923,9 +2187,7 @@ class _MatchesSemanticsData extends Matcher { ...@@ -1923,9 +2187,7 @@ class _MatchesSemanticsData extends Matcher {
final AttributedString? attributedDecreasedValue; final AttributedString? attributedDecreasedValue;
final String? tooltip; final String? tooltip;
final SemanticsHintOverrides? hintOverrides; final SemanticsHintOverrides? hintOverrides;
final List<SemanticsAction>? actions;
final List<CustomSemanticsAction>? customActions; final List<CustomSemanticsAction>? customActions;
final List<SemanticsFlag>? flags;
final TextDirection? textDirection; final TextDirection? textDirection;
final Rect? rect; final Rect? rect;
final Size? size; final Size? size;
...@@ -1936,6 +2198,14 @@ class _MatchesSemanticsData extends Matcher { ...@@ -1936,6 +2198,14 @@ class _MatchesSemanticsData extends Matcher {
final int? currentValueLength; final int? currentValueLength;
final List<Matcher>? children; final List<Matcher>? children;
/// There are three possible states for these two maps:
///
/// 1. If the flag/action maps to `true`, then it must be present in the SemanticData
/// 2. If the flag/action maps to `false`, then it must not be present in the SemanticData
/// 3. If the flag/action is not in the map, then it will not be validated against
final Map<SemanticsAction, bool> actions;
final Map<SemanticsFlag, bool> flags;
@override @override
Description describe(Description description) { Description describe(Description description) {
description.add('has semantics'); description.add('has semantics');
...@@ -1972,11 +2242,39 @@ class _MatchesSemanticsData extends Matcher { ...@@ -1972,11 +2242,39 @@ class _MatchesSemanticsData extends Matcher {
if (tooltip != null) { if (tooltip != null) {
description.add(' with tooltip: $tooltip'); description.add(' with tooltip: $tooltip');
} }
if (actions != null) { if (actions.isNotEmpty) {
description.add(' with actions: ').addDescriptionOf(actions); final List<SemanticsAction> expectedActions = actions.entries
.where((MapEntry<ui.SemanticsAction, bool> e) => e.value)
.map((MapEntry<ui.SemanticsAction, bool> e) => e.key)
.toList();
final List<SemanticsAction> notExpectedActions = actions.entries
.where((MapEntry<ui.SemanticsAction, bool> e) => !e.value)
.map((MapEntry<ui.SemanticsAction, bool> e) => e.key)
.toList();
if (expectedActions.isNotEmpty) {
description.add(' with actions: ').addDescriptionOf(expectedActions);
}
if (notExpectedActions.isNotEmpty) {
description.add(' without actions: ').addDescriptionOf(notExpectedActions);
}
} }
if (flags != null) { if (flags.isNotEmpty) {
description.add(' with flags: ').addDescriptionOf(flags); final List<SemanticsFlag> expectedFlags = flags.entries
.where((MapEntry<ui.SemanticsFlag, bool> e) => e.value)
.map((MapEntry<ui.SemanticsFlag, bool> e) => e.key)
.toList();
final List<SemanticsFlag> notExpectedFlags = flags.entries
.where((MapEntry<ui.SemanticsFlag, bool> e) => !e.value)
.map((MapEntry<ui.SemanticsFlag, bool> e) => e.key)
.toList();
if (expectedFlags.isNotEmpty) {
description.add(' with flags: ').addDescriptionOf(expectedFlags);
}
if (notExpectedFlags.isNotEmpty) {
description.add(' without flags: ').addDescriptionOf(notExpectedFlags);
}
} }
if (textDirection != null) { if (textDirection != null) {
description.add(' with textDirection: $textDirection '); description.add(' with textDirection: $textDirection ');
...@@ -2116,18 +2414,18 @@ class _MatchesSemanticsData extends Matcher { ...@@ -2116,18 +2414,18 @@ class _MatchesSemanticsData extends Matcher {
if (maxValueLength != null && maxValueLength != data.maxValueLength) { if (maxValueLength != null && maxValueLength != data.maxValueLength) {
return failWithDescription(matchState, 'maxValueLength was: ${data.maxValueLength}'); return failWithDescription(matchState, 'maxValueLength was: ${data.maxValueLength}');
} }
if (actions != null) { if (actions.isNotEmpty) {
int actionBits = 0; for (final MapEntry<ui.SemanticsAction, bool> actionEntry in actions.entries) {
for (final SemanticsAction action in actions!) { final ui.SemanticsAction action = actionEntry.key;
actionBits |= action.index; final bool actionExpected = actionEntry.value;
} final bool actionPresent = (action.index & data.actions) == action.index;
if (actionBits != data.actions) { if (actionPresent != actionExpected) {
final List<String> actionSummary = <String>[ final List<String> actionSummary = <String>[
for (final SemanticsAction action in SemanticsAction.values.values) for (final int action in SemanticsAction.values.keys)
if ((data.actions & action.index) != 0) if ((data.actions & action) != 0) describeEnum(action),
describeEnum(action), ];
]; return failWithDescription(matchState, 'actions were: $actionSummary');
return failWithDescription(matchState, 'actions were: $actionSummary'); }
} }
} }
if (customActions != null || hintOverrides != null) { if (customActions != null || hintOverrides != null) {
...@@ -2142,7 +2440,7 @@ class _MatchesSemanticsData extends Matcher { ...@@ -2142,7 +2440,7 @@ class _MatchesSemanticsData extends Matcher {
expectedCustomActions.add(CustomSemanticsAction.overridingAction(hint: hintOverrides!.onLongPressHint!, action: SemanticsAction.longPress)); expectedCustomActions.add(CustomSemanticsAction.overridingAction(hint: hintOverrides!.onLongPressHint!, action: SemanticsAction.longPress));
} }
if (expectedCustomActions.length != providedCustomActions.length) { if (expectedCustomActions.length != providedCustomActions.length) {
return failWithDescription(matchState, 'custom actions where: $providedCustomActions'); return failWithDescription(matchState, 'custom actions were: $providedCustomActions');
} }
int sortActions(CustomSemanticsAction left, CustomSemanticsAction right) { int sortActions(CustomSemanticsAction left, CustomSemanticsAction right) {
return CustomSemanticsAction.getIdentifier(left) - CustomSemanticsAction.getIdentifier(right); return CustomSemanticsAction.getIdentifier(left) - CustomSemanticsAction.getIdentifier(right);
...@@ -2151,22 +2449,22 @@ class _MatchesSemanticsData extends Matcher { ...@@ -2151,22 +2449,22 @@ class _MatchesSemanticsData extends Matcher {
providedCustomActions.sort(sortActions); providedCustomActions.sort(sortActions);
for (int i = 0; i < expectedCustomActions.length; i++) { for (int i = 0; i < expectedCustomActions.length; i++) {
if (expectedCustomActions[i] != providedCustomActions[i]) { if (expectedCustomActions[i] != providedCustomActions[i]) {
return failWithDescription(matchState, 'custom actions where: $providedCustomActions'); return failWithDescription(matchState, 'custom actions were: $providedCustomActions');
} }
} }
} }
if (flags != null) { if (flags.isNotEmpty) {
int flagBits = 0; for (final MapEntry<ui.SemanticsFlag, bool> flagEntry in flags.entries) {
for (final SemanticsFlag flag in flags!) { final ui.SemanticsFlag flag = flagEntry.key;
flagBits |= flag.index; final bool flagExpected = flagEntry.value;
} final bool flagPresent = flag.index & data.flags == flag.index;
if (flagBits != data.flags) { if (flagPresent != flagExpected) {
final List<String> flagSummary = <String>[ final List<String> flagSummary = <String>[
for (final SemanticsFlag flag in SemanticsFlag.values.values) for (final int flag in SemanticsFlag.values.keys)
if ((data.flags & flag.index) != 0) if ((data.flags & flag) != 0) describeEnum(flag),
describeEnum(flag), ];
]; return failWithDescription(matchState, 'flags were: $flagSummary');
return failWithDescription(matchState, 'flags were: $flagSummary'); }
} }
} }
bool allMatched = true; bool allMatched = true;
......
...@@ -599,10 +599,7 @@ void main() { ...@@ -599,10 +599,7 @@ void main() {
actions |= index; actions |= index;
} }
for (final int index in SemanticsFlag.values.keys) { for (final int index in SemanticsFlag.values.keys) {
// TODO(mdebbar): Remove this if after https://github.com/flutter/engine/pull/9894 flags |= index;
if (SemanticsFlag.values[index] != SemanticsFlag.isMultiline) {
flags |= index;
}
} }
final SemanticsData data = SemanticsData( final SemanticsData data = SemanticsData(
flags: flags, flags: flags,
...@@ -655,8 +652,7 @@ void main() { ...@@ -655,8 +652,7 @@ void main() {
isInMutuallyExclusiveGroup: true, isInMutuallyExclusiveGroup: true,
isHeader: true, isHeader: true,
isObscured: true, isObscured: true,
// TODO(mdebbar): Uncomment after https://github.com/flutter/engine/pull/9894 isMultiline: true,
//isMultiline: true,
namesRoute: true, namesRoute: true,
scopesRoute: true, scopesRoute: true,
isHidden: true, isHidden: true,
...@@ -721,6 +717,446 @@ void main() { ...@@ -721,6 +717,446 @@ void main() {
}); });
}); });
group('containsSemantics', () {
testWidgets('matches SemanticsData', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
addTearDown(() => handle.dispose());
const Key key = Key('semantics');
await tester.pumpWidget(Semantics(
key: key,
namesRoute: true,
header: true,
button: true,
link: true,
onTap: () { },
onLongPress: () { },
label: 'foo',
hint: 'bar',
value: 'baz',
increasedValue: 'a',
decreasedValue: 'b',
textDirection: TextDirection.rtl,
onTapHint: 'scan',
onLongPressHint: 'fill',
customSemanticsActions: <CustomSemanticsAction, VoidCallback>{
const CustomSemanticsAction(label: 'foo'): () { },
const CustomSemanticsAction(label: 'bar'): () { },
},
));
expect(
tester.getSemantics(find.byKey(key)),
containsSemantics(
label: 'foo',
hint: 'bar',
value: 'baz',
increasedValue: 'a',
decreasedValue: 'b',
textDirection: TextDirection.rtl,
hasTapAction: true,
hasLongPressAction: true,
isButton: true,
isLink: true,
isHeader: true,
namesRoute: true,
onTapHint: 'scan',
onLongPressHint: 'fill',
customActions: <CustomSemanticsAction>[
const CustomSemanticsAction(label: 'foo'),
const CustomSemanticsAction(label: 'bar'),
],
),
);
expect(
tester.getSemantics(find.byKey(key)),
isNot(containsSemantics(
label: 'foo',
hint: 'bar',
value: 'baz',
textDirection: TextDirection.rtl,
hasTapAction: true,
hasLongPressAction: true,
isButton: true,
isLink: true,
isHeader: true,
namesRoute: true,
onTapHint: 'scan',
onLongPressHint: 'fill',
customActions: <CustomSemanticsAction>[
const CustomSemanticsAction(label: 'foo'),
const CustomSemanticsAction(label: 'barz'),
],
)),
reason: 'CustomSemanticsAction "barz" should not have matched "bar".'
);
expect(
tester.getSemantics(find.byKey(key)),
isNot(matchesSemantics(
label: 'foo',
hint: 'bar',
value: 'baz',
textDirection: TextDirection.rtl,
hasTapAction: true,
hasLongPressAction: true,
isButton: true,
isLink: true,
isHeader: true,
namesRoute: true,
onTapHint: 'scans',
onLongPressHint: 'fills',
customActions: <CustomSemanticsAction>[
const CustomSemanticsAction(label: 'foo'),
const CustomSemanticsAction(label: 'bar'),
],
)),
reason: 'onTapHint "scans" should not have matched "scan".',
);
});
testWidgets('can match all semantics flags and actions enabled', (WidgetTester tester) async {
int actions = 0;
int flags = 0;
const CustomSemanticsAction action = CustomSemanticsAction(label: 'test');
for (final int index in SemanticsAction.values.keys) {
actions |= index;
}
for (final int index in SemanticsFlag.values.keys) {
flags |= index;
}
final SemanticsData data = SemanticsData(
flags: flags,
actions: actions,
attributedLabel: AttributedString('a'),
attributedIncreasedValue: AttributedString('b'),
attributedValue: AttributedString('c'),
attributedDecreasedValue: AttributedString('d'),
attributedHint: AttributedString('e'),
tooltip: 'f',
textDirection: TextDirection.ltr,
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
textSelection: null,
scrollIndex: null,
scrollChildCount: null,
scrollPosition: null,
scrollExtentMax: null,
scrollExtentMin: null,
platformViewId: 105,
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
currentValueLength: 10,
maxValueLength: 15,
);
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
expect(
node,
containsSemantics(
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
size: const Size(10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
platformViewId: 105,
currentValueLength: 10,
maxValueLength: 15,
/* Flags */
hasCheckedState: true,
isChecked: true,
isSelected: true,
isButton: true,
isSlider: true,
isKeyboardKey: true,
isLink: true,
isTextField: true,
isReadOnly: true,
hasEnabledState: true,
isFocused: true,
isFocusable: true,
isEnabled: true,
isInMutuallyExclusiveGroup: true,
isHeader: true,
isObscured: true,
isMultiline: true,
namesRoute: true,
scopesRoute: true,
isHidden: true,
isImage: true,
isLiveRegion: true,
hasToggledState: true,
isToggled: true,
hasImplicitScrolling: true,
/* Actions */
hasTapAction: true,
hasLongPressAction: true,
hasScrollLeftAction: true,
hasScrollRightAction: true,
hasScrollUpAction: true,
hasScrollDownAction: true,
hasIncreaseAction: true,
hasDecreaseAction: true,
hasShowOnScreenAction: true,
hasMoveCursorForwardByCharacterAction: true,
hasMoveCursorBackwardByCharacterAction: true,
hasMoveCursorForwardByWordAction: true,
hasMoveCursorBackwardByWordAction: true,
hasSetTextAction: true,
hasSetSelectionAction: true,
hasCopyAction: true,
hasCutAction: true,
hasPasteAction: true,
hasDidGainAccessibilityFocusAction: true,
hasDidLoseAccessibilityFocusAction: true,
hasDismissAction: true,
customActions: <CustomSemanticsAction>[action],
),
);
});
testWidgets('can match all flags and actions disabled', (WidgetTester tester) async {
final SemanticsData data = SemanticsData(
flags: 0,
actions: 0,
attributedLabel: AttributedString('a'),
attributedIncreasedValue: AttributedString('b'),
attributedValue: AttributedString('c'),
attributedDecreasedValue: AttributedString('d'),
attributedHint: AttributedString('e'),
tooltip: 'f',
textDirection: TextDirection.ltr,
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
textSelection: null,
scrollIndex: null,
scrollChildCount: null,
scrollPosition: null,
scrollExtentMax: null,
scrollExtentMin: null,
platformViewId: 105,
currentValueLength: 10,
maxValueLength: 15,
);
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
expect(
node,
containsSemantics(
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
size: const Size(10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
platformViewId: 105,
currentValueLength: 10,
maxValueLength: 15,
/* Flags */
hasCheckedState: false,
isChecked: false,
isSelected: false,
isButton: false,
isSlider: false,
isKeyboardKey: false,
isLink: false,
isTextField: false,
isReadOnly: false,
hasEnabledState: false,
isFocused: false,
isFocusable: false,
isEnabled: false,
isInMutuallyExclusiveGroup: false,
isHeader: false,
isObscured: false,
isMultiline: false,
namesRoute: false,
scopesRoute: false,
isHidden: false,
isImage: false,
isLiveRegion: false,
hasToggledState: false,
isToggled: false,
hasImplicitScrolling: false,
/* Actions */
hasTapAction: false,
hasLongPressAction: false,
hasScrollLeftAction: false,
hasScrollRightAction: false,
hasScrollUpAction: false,
hasScrollDownAction: false,
hasIncreaseAction: false,
hasDecreaseAction: false,
hasShowOnScreenAction: false,
hasMoveCursorForwardByCharacterAction: false,
hasMoveCursorBackwardByCharacterAction: false,
hasMoveCursorForwardByWordAction: false,
hasMoveCursorBackwardByWordAction: false,
hasSetTextAction: false,
hasSetSelectionAction: false,
hasCopyAction: false,
hasCutAction: false,
hasPasteAction: false,
hasDidGainAccessibilityFocusAction: false,
hasDidLoseAccessibilityFocusAction: false,
hasDismissAction: false,
),
);
});
testWidgets('only matches given flags and actions', (WidgetTester tester) async {
int allActions = 0;
int allFlags = 0;
for (final int index in SemanticsAction.values.keys) {
allActions |= index;
}
for (final int index in SemanticsFlag.values.keys) {
allFlags |= index;
}
final SemanticsData emptyData = SemanticsData(
flags: 0,
actions: 0,
attributedLabel: AttributedString('a'),
attributedIncreasedValue: AttributedString('b'),
attributedValue: AttributedString('c'),
attributedDecreasedValue: AttributedString('d'),
attributedHint: AttributedString('e'),
tooltip: 'f',
textDirection: TextDirection.ltr,
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
textSelection: null,
scrollIndex: null,
scrollChildCount: null,
scrollPosition: null,
scrollExtentMax: null,
scrollExtentMin: null,
platformViewId: 105,
currentValueLength: 10,
maxValueLength: 15,
);
final _FakeSemanticsNode emptyNode = _FakeSemanticsNode(emptyData);
const CustomSemanticsAction action = CustomSemanticsAction(label: 'test');
final SemanticsData fullData = SemanticsData(
flags: allFlags,
actions: allActions,
attributedLabel: AttributedString('a'),
attributedIncreasedValue: AttributedString('b'),
attributedValue: AttributedString('c'),
attributedDecreasedValue: AttributedString('d'),
attributedHint: AttributedString('e'),
tooltip: 'f',
textDirection: TextDirection.ltr,
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
textSelection: null,
scrollIndex: null,
scrollChildCount: null,
scrollPosition: null,
scrollExtentMax: null,
scrollExtentMin: null,
platformViewId: 105,
currentValueLength: 10,
maxValueLength: 15,
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
);
final _FakeSemanticsNode fullNode = _FakeSemanticsNode(fullData);
expect(
emptyNode,
containsSemantics(
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
size: const Size(10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
platformViewId: 105,
currentValueLength: 10,
maxValueLength: 15,
),
);
expect(
fullNode,
containsSemantics(
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
size: const Size(10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
platformViewId: 105,
currentValueLength: 10,
maxValueLength: 15,
customActions: <CustomSemanticsAction>[action],
),
);
});
testWidgets('can match child semantics', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
const Key key = Key('a');
await tester.pumpWidget(Semantics(
key: key,
label: 'Foo',
container: true,
explicitChildNodes: true,
textDirection: TextDirection.ltr,
child: Semantics(
label: 'Bar',
textDirection: TextDirection.ltr,
),
));
final SemanticsNode node = tester.getSemantics(find.byKey(key));
expect(
node,
containsSemantics(
label: 'Foo',
textDirection: TextDirection.ltr,
children: <Matcher>[
containsSemantics(
label: 'Bar',
textDirection: TextDirection.ltr,
),
],
),
);
handle.dispose();
});
testWidgets('can match only custom actions', (WidgetTester tester) async {
const CustomSemanticsAction action = CustomSemanticsAction(label: 'test');
final SemanticsData data = SemanticsData(
flags: 0,
actions: SemanticsAction.customAction.index,
attributedLabel: AttributedString('a'),
attributedIncreasedValue: AttributedString('b'),
attributedValue: AttributedString('c'),
attributedDecreasedValue: AttributedString('d'),
attributedHint: AttributedString('e'),
tooltip: 'f',
textDirection: TextDirection.ltr,
rect: const Rect.fromLTRB(0.0, 0.0, 10.0, 10.0),
elevation: 3.0,
thickness: 4.0,
textSelection: null,
scrollIndex: null,
scrollChildCount: null,
scrollPosition: null,
scrollExtentMax: null,
scrollExtentMin: null,
platformViewId: 105,
currentValueLength: 10,
maxValueLength: 15,
customSemanticsActionIds: <int>[CustomSemanticsAction.getIdentifier(action)],
);
final _FakeSemanticsNode node = _FakeSemanticsNode(data);
expect(node, containsSemantics(customActions: <CustomSemanticsAction>[action]));
});
});
group('findsAtLeastNWidgets', () { group('findsAtLeastNWidgets', () {
Widget boilerplate(Widget child) { Widget boilerplate(Widget child) {
return Directionality( return Directionality(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment