mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add accessibilityAnnouncement matcher (#180058)
Fix #180057 ## 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. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
d0e58a2d79
commit
68d3d7b421
@ -1492,8 +1492,10 @@ void main() {
|
||||
|
||||
// The initial date should be announced.
|
||||
expect(
|
||||
tester.takeAnnouncements().last.message,
|
||||
'${localizations.formatFullDate(initialDate)}$semanticLabelSuffix',
|
||||
tester.takeAnnouncements().last,
|
||||
isAccessibilityAnnouncement(
|
||||
'${localizations.formatFullDate(initialDate)}$semanticLabelSuffix',
|
||||
),
|
||||
);
|
||||
|
||||
// Select a new date.
|
||||
@ -1502,8 +1504,10 @@ void main() {
|
||||
|
||||
// The selected date should be announced.
|
||||
expect(
|
||||
tester.takeAnnouncements().last.message,
|
||||
'${localizations.selectedDateLabel} ${localizations.formatFullDate(selectedDate!)}$semanticLabelSuffix',
|
||||
tester.takeAnnouncements().last,
|
||||
isAccessibilityAnnouncement(
|
||||
'${localizations.selectedDateLabel} ${localizations.formatFullDate(selectedDate!)}$semanticLabelSuffix',
|
||||
),
|
||||
);
|
||||
|
||||
// Select the initial date.
|
||||
@ -1511,8 +1515,10 @@ void main() {
|
||||
|
||||
// The initial date should be announced as selected.
|
||||
expect(
|
||||
tester.takeAnnouncements().first.message,
|
||||
'${localizations.selectedDateLabel} ${localizations.formatFullDate(initialDate)}$semanticLabelSuffix',
|
||||
tester.takeAnnouncements().first,
|
||||
isAccessibilityAnnouncement(
|
||||
'${localizations.selectedDateLabel} ${localizations.formatFullDate(initialDate)}$semanticLabelSuffix',
|
||||
),
|
||||
);
|
||||
|
||||
semantics.dispose();
|
||||
|
||||
@ -809,7 +809,10 @@ void main() {
|
||||
// The announcement should be the opposite of the current state.
|
||||
// The ExpansionTile is expanded, so the announcement should be
|
||||
// "Expanded".
|
||||
expect(tester.takeAnnouncements().first.message, localizations.collapsedHint);
|
||||
expect(
|
||||
tester.takeAnnouncements().first,
|
||||
isAccessibilityAnnouncement(localizations.collapsedHint),
|
||||
);
|
||||
|
||||
// Tap the title to collapse ExpansionTile.
|
||||
await tester.tap(find.text('Title'));
|
||||
@ -818,7 +821,10 @@ void main() {
|
||||
// The announcement should be the opposite of the current state.
|
||||
// The ExpansionTile is collapsed, so the announcement should be
|
||||
// "Collapsed".
|
||||
expect(tester.takeAnnouncements().first.message, localizations.expandedHint);
|
||||
expect(
|
||||
tester.takeAnnouncements().first,
|
||||
isAccessibilityAnnouncement(localizations.expandedHint),
|
||||
);
|
||||
handle.dispose();
|
||||
},
|
||||
// [intended] https://github.com/flutter/flutter/issues/122101.
|
||||
@ -849,13 +855,19 @@ void main() {
|
||||
await tester.tap(find.text('Title'));
|
||||
await tester.pump(const Duration(seconds: 1)); // Wait for the announcement to be made.
|
||||
|
||||
expect(tester.takeAnnouncements().first.message, localizations.collapsedHint);
|
||||
expect(
|
||||
tester.takeAnnouncements().first,
|
||||
isAccessibilityAnnouncement(localizations.collapsedHint),
|
||||
);
|
||||
|
||||
// Tap the title to collapse ExpansionTile.
|
||||
await tester.tap(find.text('Title'));
|
||||
await tester.pump(const Duration(seconds: 1)); // Wait for the announcement to be made.
|
||||
|
||||
expect(tester.takeAnnouncements().first.message, localizations.expandedHint);
|
||||
expect(
|
||||
tester.takeAnnouncements().first,
|
||||
isAccessibilityAnnouncement(localizations.expandedHint),
|
||||
);
|
||||
handle.dispose();
|
||||
},
|
||||
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
||||
|
||||
@ -3415,7 +3415,10 @@ void main() {
|
||||
await tester.pump();
|
||||
expect(find.byKey(optionsKey), findsOneWidget);
|
||||
expect(lastOptions.length, kOptions.length);
|
||||
expect(tester.takeAnnouncements().first.message, localizations.searchResultsFound);
|
||||
expect(
|
||||
tester.takeAnnouncements().first,
|
||||
isAccessibilityAnnouncement(localizations.searchResultsFound),
|
||||
);
|
||||
|
||||
await tester.enterText(find.byKey(fieldKey), 'a');
|
||||
await tester.pump();
|
||||
@ -3426,7 +3429,10 @@ void main() {
|
||||
await tester.enterText(find.byKey(fieldKey), 'zzzz');
|
||||
await tester.pump();
|
||||
expect(find.byKey(optionsKey), findsNothing);
|
||||
expect(tester.takeAnnouncements().first.message, localizations.noResultsFound);
|
||||
expect(
|
||||
tester.takeAnnouncements().first,
|
||||
isAccessibilityAnnouncement(localizations.noResultsFound),
|
||||
);
|
||||
|
||||
handle.dispose();
|
||||
});
|
||||
|
||||
@ -219,15 +219,15 @@ void main() {
|
||||
expect(find.text('Second error message'), findsOneWidget);
|
||||
|
||||
if (test.supportsAnnounce) {
|
||||
final CapturedAccessibilityAnnouncement announcement = tester.takeAnnouncements().single;
|
||||
expect(announcement.message, 'First error message');
|
||||
expect(announcement.textDirection, TextDirection.ltr);
|
||||
expect(announcement.assertiveness, Assertiveness.assertive);
|
||||
expect(tester.takeAnnouncements(), [
|
||||
isAccessibilityAnnouncement(
|
||||
'First error message',
|
||||
textDirection: TextDirection.ltr,
|
||||
assertiveness: Assertiveness.assertive,
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
final CapturedAccessibilityAnnouncement? announcement = tester
|
||||
.takeAnnouncements()
|
||||
.firstOrNull;
|
||||
expect(announcement, null);
|
||||
expect(tester.takeAnnouncements(), isEmpty);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -443,10 +443,13 @@ void main() {
|
||||
await tester.pump();
|
||||
expect(find.text('error'), findsOneWidget);
|
||||
|
||||
final CapturedAccessibilityAnnouncement announcement = tester.takeAnnouncements().single;
|
||||
expect(announcement.message, 'error');
|
||||
expect(announcement.textDirection, TextDirection.ltr);
|
||||
expect(announcement.assertiveness, Assertiveness.assertive);
|
||||
expect(tester.takeAnnouncements(), [
|
||||
isAccessibilityAnnouncement(
|
||||
'error',
|
||||
textDirection: TextDirection.ltr,
|
||||
assertiveness: Assertiveness.assertive,
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('Multiple TextFormFields communicate', (WidgetTester tester) async {
|
||||
|
||||
@ -1042,6 +1042,35 @@ Matcher containsSemantics({
|
||||
);
|
||||
}
|
||||
|
||||
/// Asserts that a [CapturedAccessibilityAnnouncement] matches the expected message.
|
||||
///
|
||||
/// The [message] argument matches the [CapturedAccessibilityAnnouncement.message].
|
||||
/// The [textDirection] argument, if non-null, matches the [CapturedAccessibilityAnnouncement.textDirection].
|
||||
/// The [assertiveness] argument, if non-null, matches the [CapturedAccessibilityAnnouncement.assertiveness].
|
||||
///
|
||||
/// ## Sample code
|
||||
///
|
||||
/// ```dart
|
||||
/// await SemanticsService.sendAnnouncement(tester.view, 'Hello', TextDirection.ltr);
|
||||
/// expect(tester.takeAnnouncements(), contains(isAccessibilityAnnouncement('Hello')));
|
||||
/// ```
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [WidgetTester.takeAnnouncements], which retrieves the announcements in unit tests.
|
||||
/// * [SemanticsService.sendAnnouncement], which sends an announcement.
|
||||
Matcher isAccessibilityAnnouncement(
|
||||
String message, {
|
||||
TextDirection? textDirection,
|
||||
Assertiveness? assertiveness,
|
||||
}) {
|
||||
return _MatchesAccessibilityAnnouncement(
|
||||
expectedMessage: message,
|
||||
expectedTextDirection: textDirection,
|
||||
expectedAssertiveness: assertiveness,
|
||||
);
|
||||
}
|
||||
|
||||
/// Asserts that the currently rendered widget meets the provided accessibility
|
||||
/// `guideline`.
|
||||
///
|
||||
@ -2966,6 +2995,40 @@ class _MatchesSemanticsData extends Matcher {
|
||||
}
|
||||
}
|
||||
|
||||
class _MatchesAccessibilityAnnouncement extends Matcher {
|
||||
const _MatchesAccessibilityAnnouncement({
|
||||
required this.expectedMessage,
|
||||
required this.expectedTextDirection,
|
||||
required this.expectedAssertiveness,
|
||||
});
|
||||
|
||||
final String expectedMessage;
|
||||
final TextDirection? expectedTextDirection;
|
||||
final Assertiveness? expectedAssertiveness;
|
||||
|
||||
@override
|
||||
bool matches(
|
||||
covariant CapturedAccessibilityAnnouncement event,
|
||||
Map<dynamic, dynamic> matchState,
|
||||
) {
|
||||
return event.message == expectedMessage &&
|
||||
(expectedTextDirection == null || event.textDirection == expectedTextDirection) &&
|
||||
(expectedAssertiveness == null || event.assertiveness == expectedAssertiveness);
|
||||
}
|
||||
|
||||
@override
|
||||
Description describe(Description description) {
|
||||
description.add('Semantic announcement with message "$expectedMessage"');
|
||||
if (expectedTextDirection != null) {
|
||||
description.add(', textDirection: $expectedTextDirection');
|
||||
}
|
||||
if (expectedAssertiveness != null) {
|
||||
description.add(', assertiveness: $expectedAssertiveness');
|
||||
}
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
class _MatchesAccessibilityGuideline extends AsyncMatcher {
|
||||
_MatchesAccessibilityGuideline(this.guideline);
|
||||
|
||||
|
||||
@ -704,24 +704,21 @@ void main() {
|
||||
);
|
||||
await SemanticsService.sendAnnouncement(tester.view, 'announcement 3', TextDirection.rtl);
|
||||
|
||||
final List<CapturedAccessibilityAnnouncement> list = tester.takeAnnouncements();
|
||||
expect(list, hasLength(3));
|
||||
final CapturedAccessibilityAnnouncement first = list[0];
|
||||
expect(first.message, 'announcement 1');
|
||||
expect(first.textDirection, TextDirection.ltr);
|
||||
expect(tester.takeAnnouncements(), [
|
||||
isAccessibilityAnnouncement('announcement 1', textDirection: TextDirection.ltr),
|
||||
isAccessibilityAnnouncement(
|
||||
'announcement 2',
|
||||
textDirection: TextDirection.rtl,
|
||||
assertiveness: Assertiveness.assertive,
|
||||
),
|
||||
isAccessibilityAnnouncement(
|
||||
'announcement 3',
|
||||
textDirection: TextDirection.rtl,
|
||||
assertiveness: Assertiveness.polite,
|
||||
),
|
||||
]);
|
||||
|
||||
final CapturedAccessibilityAnnouncement second = list[1];
|
||||
expect(second.message, 'announcement 2');
|
||||
expect(second.textDirection, TextDirection.rtl);
|
||||
expect(second.assertiveness, Assertiveness.assertive);
|
||||
|
||||
final CapturedAccessibilityAnnouncement third = list[2];
|
||||
expect(third.message, 'announcement 3');
|
||||
expect(third.textDirection, TextDirection.rtl);
|
||||
expect(third.assertiveness, Assertiveness.polite);
|
||||
|
||||
final List<CapturedAccessibilityAnnouncement> emptyList = tester.takeAnnouncements();
|
||||
expect(emptyList, <CapturedAccessibilityAnnouncement>[]);
|
||||
expect(tester.takeAnnouncements(), <CapturedAccessibilityAnnouncement>[]);
|
||||
});
|
||||
|
||||
testWidgets('New test API is not breaking existing tests', (WidgetTester tester) async {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user