@@ -62,6 +62,11 @@ enum EnginePhase {
6262 sendSemanticsUpdate,
6363}
6464
65+ /// Signature of callbacks used to intercept messages on a given channel.
66+ ///
67+ /// See [TestDefaultBinaryMessenger.setMockDecodedMessageHandler] for more details.
68+ typedef _MockMessageHandler = Future <void > Function (Object ? );
69+
6570/// Parts of the system that can generate pointer events that reach the test
6671/// binding.
6772///
@@ -106,6 +111,32 @@ mixin TestDefaultBinaryMessengerBinding on BindingBase, ServicesBinding {
106111 }
107112}
108113
114+ /// Accessibility announcement data passed to [SemanticsService.announce] captured in a test.
115+ ///
116+ /// This class is intended to be used by the testing API to store the announcements
117+ /// in a structured form so that tests can verify announcement details. The fields
118+ /// of this class correspond to parameters of the [SemanticsService.announce] method.
119+ ///
120+ /// See also:
121+ ///
122+ /// * [WidgetTester.takeAnnouncements] , which is the test API that uses this class.
123+ class CapturedAccessibilityAnnouncement {
124+ const CapturedAccessibilityAnnouncement ._(
125+ this .message,
126+ this .textDirection,
127+ this .assertiveness,
128+ );
129+
130+ /// The accessibility message announced by the framework.
131+ final String message;
132+
133+ /// The direction in which the text of the [message] flows.
134+ final TextDirection textDirection;
135+
136+ /// Determines the assertiveness level of the accessibility announcement.
137+ final Assertiveness assertiveness;
138+ }
139+
109140/// Base class for bindings used by widgets library tests.
110141///
111142/// The [ensureInitialized] method creates (if necessary) and returns an
@@ -611,6 +642,24 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
611642 late StackTraceDemangler _oldStackTraceDemangler;
612643 FlutterErrorDetails ? _pendingExceptionDetails;
613644
645+ _MockMessageHandler ? _announcementHandler;
646+ List <CapturedAccessibilityAnnouncement > _announcements =
647+ < CapturedAccessibilityAnnouncement > [];
648+
649+ /// {@template flutter.flutter_test.TakeAccessibilityAnnouncements}
650+ /// Returns a list of all the accessibility announcements made by the Flutter
651+ /// framework since the last time this function was called.
652+ ///
653+ /// It's safe to call this when there hasn't been any announcements; it will return
654+ /// an empty list in that case.
655+ /// {@endtemplate}
656+ List <CapturedAccessibilityAnnouncement > takeAnnouncements () {
657+ assert (inTest);
658+ final List <CapturedAccessibilityAnnouncement > announcements = _announcements;
659+ _announcements = < CapturedAccessibilityAnnouncement > [];
660+ return announcements;
661+ }
662+
614663 static const TextStyle _messageStyle = TextStyle (
615664 color: Color (0xFF917FFF ),
616665 fontSize: 40.0 ,
@@ -700,13 +749,41 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
700749 // The LiveTestWidgetsFlutterBinding overrides this to report the exception to the console.
701750 }
702751
752+ Future <void > _handleAnnouncementMessage (Object ? mockMessage) async {
753+ final Map <Object ?, Object ?> message = mockMessage! as Map <Object ?, Object ?>;
754+ if (message['type' ] == 'announce' ) {
755+ final Map <Object ?, Object ?> data =
756+ message['data' ]! as Map <Object ?, Object ?>;
757+ final String dataMessage = data['message' ].toString ();
758+ final TextDirection textDirection =
759+ TextDirection .values[data['textDirection' ]! as int ];
760+ final int assertivenessLevel = (data['assertiveness' ] as int ? ) ?? 0 ;
761+ final Assertiveness assertiveness =
762+ Assertiveness .values[assertivenessLevel];
763+ final CapturedAccessibilityAnnouncement announcement =
764+ CapturedAccessibilityAnnouncement ._(
765+ dataMessage, textDirection, assertiveness);
766+ _announcements.add (announcement);
767+ }
768+ }
769+
703770 Future <void > _runTest (
704771 Future <void > Function () testBody,
705772 VoidCallback invariantTester,
706773 String description,
707774 ) {
708775 assert (description != null );
709776 assert (inTest);
777+
778+ // Set the handler only if there is currently none.
779+ if (TestDefaultBinaryMessengerBinding .instance! .defaultBinaryMessenger
780+ .checkMockMessageHandler (SystemChannels .accessibility.name, null )) {
781+ _announcementHandler = _handleAnnouncementMessage;
782+ TestDefaultBinaryMessengerBinding .instance! .defaultBinaryMessenger
783+ .setMockDecodedMessageHandler <dynamic >(
784+ SystemChannels .accessibility, _announcementHandler);
785+ }
786+
710787 _oldExceptionHandler = FlutterError .onError;
711788 _oldStackTraceDemangler = FlutterError .demangleStackTrace;
712789 int exceptionCount = 0 ; // number of un-taken exceptions
@@ -988,6 +1065,15 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
9881065 _parentZone = null ;
9891066 buildOwner! .focusManager.dispose ();
9901067
1068+ if (TestDefaultBinaryMessengerBinding .instance! .defaultBinaryMessenger
1069+ .checkMockMessageHandler (
1070+ SystemChannels .accessibility.name, _announcementHandler)) {
1071+ TestDefaultBinaryMessengerBinding .instance! .defaultBinaryMessenger
1072+ .setMockDecodedMessageHandler (SystemChannels .accessibility, null );
1073+ _announcementHandler = null ;
1074+ }
1075+ _announcements = < CapturedAccessibilityAnnouncement > [];
1076+
9911077 ServicesBinding .instance.keyEventManager.keyMessageHandler = null ;
9921078 buildOwner! .focusManager = FocusManager ()..registerGlobalHandlers ();
9931079
0 commit comments