mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add error description for nbsp character(\u202f) (#178895)
This PR is to add a more clear error description when there is a special white space NNBSP(\u202f). This whitespace character is used to connect time and "am"/"pm". In this PR, I only updated the description in `_MatchesSemanticsData` and still not sure if it's necessary to create another custom matcher class to ignore special whitespace. I think it's valid to directly update users' test results since the expected results have indeed changed. This is my experiment for the whitespace change when we update generated_date_localization.dart: cl/831625146 I went through all Scuba diffs and all results look expected. Once this is landed, we can use the CL change (except the image change) as g3fix to update the date localizations. Fixes https://github.com/flutter/flutter/issues/177479 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing.
This commit is contained in:
parent
a045e04710
commit
08e122f466
@ -2564,37 +2564,41 @@ class _MatchesSemanticsData extends Matcher {
|
||||
Description describe(Description description, [String? index]) {
|
||||
description.add('${index == null ? '' : 'Child $index '}has semantics');
|
||||
if (label != null) {
|
||||
description.add(' with label: $label');
|
||||
description.add(' with label: ${_escape(label!)}');
|
||||
}
|
||||
if (attributedLabel != null) {
|
||||
description.add(' with attributedLabel: $attributedLabel');
|
||||
description.add(' with attributedLabel: ${_escapeAttributedString(attributedLabel!)}');
|
||||
}
|
||||
if (value != null) {
|
||||
description.add(' with value: $value');
|
||||
description.add(' with value: ${_escape(value!)}');
|
||||
}
|
||||
if (attributedValue != null) {
|
||||
description.add(' with attributedValue: $attributedValue');
|
||||
description.add(' with attributedValue: ${_escapeAttributedString(attributedValue!)}');
|
||||
}
|
||||
if (hint != null) {
|
||||
description.add(' with hint: $hint');
|
||||
description.add(' with hint: ${_escape(hint!)}');
|
||||
}
|
||||
if (attributedHint != null) {
|
||||
description.add(' with attributedHint: $attributedHint');
|
||||
description.add(' with attributedHint: ${_escapeAttributedString(attributedHint!)}');
|
||||
}
|
||||
if (increasedValue != null) {
|
||||
description.add(' with increasedValue: $increasedValue ');
|
||||
description.add(' with increasedValue: ${_escape(increasedValue!)} ');
|
||||
}
|
||||
if (attributedIncreasedValue != null) {
|
||||
description.add(' with attributedIncreasedValue: $attributedIncreasedValue');
|
||||
description.add(
|
||||
' with attributedIncreasedValue: ${_escapeAttributedString(attributedIncreasedValue!)}',
|
||||
);
|
||||
}
|
||||
if (decreasedValue != null) {
|
||||
description.add(' with decreasedValue: $decreasedValue ');
|
||||
description.add(' with decreasedValue: ${_escape(decreasedValue!)} ');
|
||||
}
|
||||
if (attributedDecreasedValue != null) {
|
||||
description.add(' with attributedDecreasedValue: $attributedDecreasedValue');
|
||||
description.add(
|
||||
' with attributedDecreasedValue: ${_escapeAttributedString(attributedDecreasedValue!)}',
|
||||
);
|
||||
}
|
||||
if (tooltip != null) {
|
||||
description.add(' with tooltip: $tooltip');
|
||||
description.add(' with tooltip: ${_escape(tooltip!)}');
|
||||
}
|
||||
if (inputType != null) {
|
||||
description.add(' with inputType: $inputType');
|
||||
@ -2695,6 +2699,27 @@ class _MatchesSemanticsData extends Matcher {
|
||||
return true;
|
||||
}
|
||||
|
||||
static String _escape(String string) => string.replaceAll('\u202f', r'\u202f');
|
||||
|
||||
static AttributedString _escapeAttributedString(AttributedString attributedString) {
|
||||
return AttributedString(
|
||||
_escape(attributedString.string),
|
||||
attributes: attributedString.attributes,
|
||||
);
|
||||
}
|
||||
|
||||
bool _checkStringMismatch(Map<dynamic, dynamic> matchState, String prefixText, String actual) {
|
||||
return failWithDescription(matchState, '$prefixText was ${_escape(actual)}');
|
||||
}
|
||||
|
||||
bool _checkStringAttributeMismatch(
|
||||
Map<dynamic, dynamic> matchState,
|
||||
String prefixText,
|
||||
AttributedString actual,
|
||||
) {
|
||||
return failWithDescription(matchState, '$prefixText was: ${_escapeAttributedString(actual)}');
|
||||
}
|
||||
|
||||
@override
|
||||
bool matches(dynamic node, Map<dynamic, dynamic> matchState) {
|
||||
if (node == null) {
|
||||
@ -2712,7 +2737,7 @@ class _MatchesSemanticsData extends Matcher {
|
||||
};
|
||||
|
||||
if (label != null && label != data.label) {
|
||||
return failWithDescription(matchState, 'label was: ${data.label}');
|
||||
return _checkStringMismatch(matchState, 'label', data.label);
|
||||
}
|
||||
if (attributedLabel != null &&
|
||||
(attributedLabel!.string != data.attributedLabel.string ||
|
||||
@ -2720,18 +2745,18 @@ class _MatchesSemanticsData extends Matcher {
|
||||
attributedLabel!.attributes,
|
||||
data.attributedLabel.attributes,
|
||||
))) {
|
||||
return failWithDescription(matchState, 'attributedLabel was: ${data.attributedLabel}');
|
||||
return _checkStringAttributeMismatch(matchState, 'attributedLabel', data.attributedLabel);
|
||||
}
|
||||
if (hint != null && hint != data.hint) {
|
||||
return failWithDescription(matchState, 'hint was: ${data.hint}');
|
||||
return _checkStringMismatch(matchState, 'hint', data.hint);
|
||||
}
|
||||
if (attributedHint != null &&
|
||||
(attributedHint!.string != data.attributedHint.string ||
|
||||
!_stringAttributesEqual(attributedHint!.attributes, data.attributedHint.attributes))) {
|
||||
return failWithDescription(matchState, 'attributedHint was: ${data.attributedHint}');
|
||||
return _checkStringAttributeMismatch(matchState, 'attributedHint', data.attributedHint);
|
||||
}
|
||||
if (value != null && value != data.value) {
|
||||
return failWithDescription(matchState, 'value was: ${data.value}');
|
||||
return _checkStringMismatch(matchState, 'value', data.value);
|
||||
}
|
||||
if (attributedValue != null &&
|
||||
(attributedValue!.string != data.attributedValue.string ||
|
||||
@ -2739,10 +2764,10 @@ class _MatchesSemanticsData extends Matcher {
|
||||
attributedValue!.attributes,
|
||||
data.attributedValue.attributes,
|
||||
))) {
|
||||
return failWithDescription(matchState, 'attributedValue was: ${data.attributedValue}');
|
||||
return _checkStringAttributeMismatch(matchState, 'attributedValue', data.attributedValue);
|
||||
}
|
||||
if (increasedValue != null && increasedValue != data.increasedValue) {
|
||||
return failWithDescription(matchState, 'increasedValue was: ${data.increasedValue}');
|
||||
return _checkStringMismatch(matchState, 'increasedValue', data.increasedValue);
|
||||
}
|
||||
if (attributedIncreasedValue != null &&
|
||||
(attributedIncreasedValue!.string != data.attributedIncreasedValue.string ||
|
||||
@ -2750,13 +2775,14 @@ class _MatchesSemanticsData extends Matcher {
|
||||
attributedIncreasedValue!.attributes,
|
||||
data.attributedIncreasedValue.attributes,
|
||||
))) {
|
||||
return failWithDescription(
|
||||
return _checkStringAttributeMismatch(
|
||||
matchState,
|
||||
'attributedIncreasedValue was: ${data.attributedIncreasedValue}',
|
||||
'attributedIncreasedValue',
|
||||
data.attributedIncreasedValue,
|
||||
);
|
||||
}
|
||||
if (decreasedValue != null && decreasedValue != data.decreasedValue) {
|
||||
return failWithDescription(matchState, 'decreasedValue was: ${data.decreasedValue}');
|
||||
return _checkStringMismatch(matchState, 'decreasedValue', data.decreasedValue);
|
||||
}
|
||||
if (attributedDecreasedValue != null &&
|
||||
(attributedDecreasedValue!.string != data.attributedDecreasedValue.string ||
|
||||
@ -2764,13 +2790,14 @@ class _MatchesSemanticsData extends Matcher {
|
||||
attributedDecreasedValue!.attributes,
|
||||
data.attributedDecreasedValue.attributes,
|
||||
))) {
|
||||
return failWithDescription(
|
||||
return _checkStringAttributeMismatch(
|
||||
matchState,
|
||||
'attributedDecreasedValue was: ${data.attributedDecreasedValue}',
|
||||
'attributedDecreasedValue',
|
||||
data.attributedDecreasedValue,
|
||||
);
|
||||
}
|
||||
if (tooltip != null && tooltip != data.tooltip) {
|
||||
return failWithDescription(matchState, 'tooltip was: ${data.tooltip}');
|
||||
return _checkStringMismatch(matchState, 'tooltip', data.tooltip);
|
||||
}
|
||||
if (textDirection != null && textDirection != data.textDirection) {
|
||||
return failWithDescription(matchState, 'textDirection was: $textDirection');
|
||||
|
||||
@ -913,6 +913,364 @@ void main() {
|
||||
expect(failedExpectation, throwsA(isA<TestFailure>()));
|
||||
handle.dispose();
|
||||
});
|
||||
|
||||
testWidgets('matchesSemantics captures NBSP in failure descriptions', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final data = SemanticsData(
|
||||
flagsCollection: SemanticsFlags.none,
|
||||
actions: 0,
|
||||
identifier: '',
|
||||
traversalChildIdentifier: '',
|
||||
traversalParentIdentifier: '',
|
||||
attributedLabel: AttributedString('label\u202f0'),
|
||||
attributedHint: AttributedString('hint\u202f0'),
|
||||
attributedValue: AttributedString('value\u202f0'),
|
||||
attributedIncreasedValue: AttributedString('increasedValue\u202f0'),
|
||||
attributedDecreasedValue: AttributedString('decreasedValue\u202f0'),
|
||||
tooltip: 'tooltip\u202f0',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: Rect.zero,
|
||||
textSelection: null,
|
||||
scrollIndex: null,
|
||||
scrollChildCount: null,
|
||||
scrollPosition: null,
|
||||
scrollExtentMax: null,
|
||||
scrollExtentMin: null,
|
||||
platformViewId: 0,
|
||||
maxValueLength: 0,
|
||||
currentValueLength: 0,
|
||||
headingLevel: 0,
|
||||
linkUrl: null,
|
||||
role: ui.SemanticsRole.none,
|
||||
controlsNodes: null,
|
||||
validationResult: SemanticsValidationResult.none,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
hitTestBehavior: ui.SemanticsHitTestBehavior.defer,
|
||||
);
|
||||
final node = _FakeSemanticsNode(data);
|
||||
|
||||
// Label
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(label: 'label 0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r'label was label\u202f0'),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(attributedLabel: AttributedString('label 0'))),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r"attributedLabel was: AttributedString('label\u202f0'"),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Hint
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(hint: 'hint 0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r'hint was hint\u202f0'),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(attributedHint: AttributedString('hint 0'))),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r"attributedHint was: AttributedString('hint\u202f0'"),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Value
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(value: 'value 0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r'value was value\u202f0'),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(attributedValue: AttributedString('value 0'))),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r"attributedValue was: AttributedString('value\u202f0'"),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Increased Value
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(increasedValue: 'increasedValue 0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r'increasedValue was increasedValue\u202f0'),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
() => expect(
|
||||
node,
|
||||
matchesSemantics(attributedIncreasedValue: AttributedString('increasedValue 0')),
|
||||
),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r"attributedIncreasedValue was: AttributedString('increasedValue\u202f0'"),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Decreased Value
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(decreasedValue: 'decreasedValue 0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r'decreasedValue was decreasedValue\u202f0'),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
() => expect(
|
||||
node,
|
||||
matchesSemantics(attributedDecreasedValue: AttributedString('decreasedValue 0')),
|
||||
),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r"attributedDecreasedValue was: AttributedString('decreasedValue\u202f0'"),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Tooltip
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(tooltip: 'tooltip 0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
contains(r'tooltip was tooltip\u202f0'),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'matchesSemantics shows correct error message when actuals have normal space but expects have NBSP',
|
||||
(WidgetTester tester) async {
|
||||
final data = SemanticsData(
|
||||
flagsCollection: SemanticsFlags.none,
|
||||
actions: 0,
|
||||
identifier: '',
|
||||
traversalChildIdentifier: '',
|
||||
traversalParentIdentifier: '',
|
||||
attributedLabel: AttributedString('label 0'),
|
||||
attributedHint: AttributedString('hint 0'),
|
||||
attributedValue: AttributedString('value 0'),
|
||||
attributedIncreasedValue: AttributedString('increasedValue 0'),
|
||||
attributedDecreasedValue: AttributedString('decreasedValue 0'),
|
||||
tooltip: 'tooltip 0',
|
||||
textDirection: TextDirection.ltr,
|
||||
rect: Rect.zero,
|
||||
textSelection: null,
|
||||
scrollIndex: null,
|
||||
scrollChildCount: null,
|
||||
scrollPosition: null,
|
||||
scrollExtentMax: null,
|
||||
scrollExtentMin: null,
|
||||
platformViewId: 0,
|
||||
maxValueLength: 0,
|
||||
currentValueLength: 0,
|
||||
headingLevel: 0,
|
||||
linkUrl: null,
|
||||
role: ui.SemanticsRole.none,
|
||||
controlsNodes: null,
|
||||
validationResult: SemanticsValidationResult.none,
|
||||
inputType: ui.SemanticsInputType.none,
|
||||
locale: null,
|
||||
hitTestBehavior: ui.SemanticsHitTestBehavior.defer,
|
||||
);
|
||||
final node = _FakeSemanticsNode(data);
|
||||
|
||||
// Label
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(label: 'label\u202f0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(contains(r'with label: label\u202f0'), contains(r'label was label 0')),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(attributedLabel: AttributedString('label\u202f0'))),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(
|
||||
contains(r"with attributedLabel: AttributedString('label\u202f0'"),
|
||||
contains(r"attributedLabel was: AttributedString('label 0'"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Hint
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(hint: 'hint\u202f0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(contains(r'with hint: hint\u202f0'), contains(r'hint was hint 0')),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(attributedHint: AttributedString('hint\u202f0'))),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(
|
||||
contains(r"with attributedHint: AttributedString('hint\u202f0'"),
|
||||
contains(r"attributedHint was: AttributedString('hint 0'"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Value
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(value: 'value\u202f0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(contains(r'with value: value\u202f0'), contains(r'value was value 0')),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(attributedValue: AttributedString('value\u202f0'))),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(
|
||||
contains(r"with attributedValue: AttributedString('value\u202f0'"),
|
||||
contains(r"attributedValue was: AttributedString('value 0'"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Increased Value
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(increasedValue: 'increasedValue\u202f0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(
|
||||
contains(r'with increasedValue: increasedValue\u202f0'),
|
||||
contains(r'increasedValue was increasedValue 0'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
() => expect(
|
||||
node,
|
||||
matchesSemantics(attributedIncreasedValue: AttributedString('increasedValue\u202f0')),
|
||||
),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(
|
||||
contains(
|
||||
r"with attributedIncreasedValue: AttributedString('increasedValue\u202f0'",
|
||||
),
|
||||
contains(r"attributedIncreasedValue was: AttributedString('increasedValue 0'"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Decreased Value
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(decreasedValue: 'decreasedValue\u202f0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(
|
||||
contains(r'with decreasedValue: decreasedValue\u202f0'),
|
||||
contains(r'decreasedValue was decreasedValue 0'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
() => expect(
|
||||
node,
|
||||
matchesSemantics(attributedDecreasedValue: AttributedString('decreasedValue\u202f0')),
|
||||
),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(
|
||||
contains(
|
||||
r"with attributedDecreasedValue: AttributedString('decreasedValue\u202f0'",
|
||||
),
|
||||
contains(r"attributedDecreasedValue was: AttributedString('decreasedValue 0'"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Tooltip
|
||||
expect(
|
||||
() => expect(node, matchesSemantics(tooltip: 'tooltip\u202f0')),
|
||||
throwsA(
|
||||
isA<TestFailure>().having(
|
||||
(TestFailure e) => e.message,
|
||||
'message',
|
||||
allOf(contains(r'with tooltip: tooltip\u202f0'), contains(r'tooltip was tooltip 0')),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
group('containsSemantics', () {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user