2222using flutter::testing::keycodes::kLogicalKeyM ;
2323using flutter::testing::keycodes::kLogicalKeyQ ;
2424using flutter::testing::keycodes::kLogicalKeyT ;
25+ using flutter::testing::keycodes::kPhysicalKeyA ;
2526
2627using flutter::LayoutClue;
2728
@@ -211,6 +212,10 @@ - (void)respondTextInputWith:(BOOL)response;
211212- (void )recordCallTypesTo : (nonnull NSMutableArray <NSNumber*>*)typeStorage
212213 forTypes : (uint32_t )typeMask ;
213214
215+ - (id )lastKeyboardChannelResult ;
216+
217+ - (void )sendKeyboardChannelMessage : (NSData * _Nullable)message ;
218+
214219@property (readonly , nonatomic , strong ) FlutterKeyboardManager* manager;
215220@property (nonatomic , nullable , strong ) NSResponder * nextResponder;
216221
@@ -237,6 +242,10 @@ @implementation KeyboardTester {
237242
238243 flutter::KeyboardLayoutNotifier _keyboardLayoutNotifier;
239244 const MockLayoutData* _currentLayout;
245+
246+ id _keyboardChannelResult;
247+ NSObject <FlutterBinaryMessenger>* _messengerMock;
248+ FlutterBinaryMessageHandler _keyboardHandler;
240249}
241250
242251- (nonnull instancetype )init {
@@ -252,17 +261,21 @@ - (nonnull instancetype)init {
252261
253262 _currentLayout = &kUsLayout ;
254263
255- id messengerMock = OCMStrictProtocolMock (@protocol (FlutterBinaryMessenger));
256- OCMStub ([messengerMock sendOnChannel: @" flutter/keyevent"
257- message: [OCMArg any ]
258- binaryReply: [OCMArg any ]])
264+ _messengerMock = OCMStrictProtocolMock (@protocol (FlutterBinaryMessenger));
265+ OCMStub ([_messengerMock sendOnChannel: @" flutter/keyevent"
266+ message: [OCMArg any ]
267+ binaryReply: [OCMArg any ]])
259268 .andCall (self, @selector (handleChannelMessage:message:binaryReply: ));
260-
269+ OCMStub ([_messengerMock setMessageHandlerOnChannel: @" flutter/keyboard"
270+ binaryMessageHandler: [OCMArg any ]])
271+ .andCall (self, @selector (setKeyboardChannelHandler:handler: ));
272+ OCMStub ([_messengerMock sendOnChannel: @" flutter/keyboard" message: [OCMArg any ]])
273+ .andCall (self, @selector (handleKeyboardChannelMessage:message: ));
261274 id viewDelegateMock = OCMStrictProtocolMock (@protocol (FlutterKeyboardViewDelegate));
262275 OCMStub ([viewDelegateMock nextResponder ]).andReturn (_nextResponder);
263276 OCMStub ([viewDelegateMock onTextInputKeyEvent: [OCMArg any ]])
264277 .andCall (self, @selector (handleTextInputKeyEvent: ));
265- OCMStub ([viewDelegateMock getBinaryMessenger ]).andReturn (messengerMock );
278+ OCMStub ([viewDelegateMock getBinaryMessenger ]).andReturn (_messengerMock );
266279 OCMStub ([viewDelegateMock sendKeyEvent: FlutterKeyEvent {} callback: nil userData: nil ])
267280 .ignoringNonObjectArgs ()
268281 .andCall (self, @selector (handleEmbedderEvent:callback:userData: ));
@@ -276,6 +289,10 @@ - (nonnull instancetype)init {
276289 return self;
277290}
278291
292+ - (id )lastKeyboardChannelResult {
293+ return _keyboardChannelResult;
294+ }
295+
279296- (void )respondEmbedderCallsWith : (BOOL )response {
280297 _embedderHandler = ^(const FlutterKeyEvent* event, FlutterAsyncKeyCallback callback) {
281298 callback (response);
@@ -327,6 +344,10 @@ - (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
327344 _typeStorageMask = typeMask;
328345}
329346
347+ - (void )sendKeyboardChannelMessage : (NSData * _Nullable)message {
348+ [_messengerMock sendOnChannel: @" flutter/keyboard" message: message];
349+ }
350+
330351- (void )setLayout : (const MockLayoutData&)layout {
331352 _currentLayout = &layout;
332353 if (_keyboardLayoutNotifier != nil ) {
@@ -364,6 +385,12 @@ - (void)handleChannelMessage:(NSString*)channel
364385 });
365386}
366387
388+ - (void )handleKeyboardChannelMessage : (NSString *)channel message : (NSData * _Nullable)message {
389+ _keyboardHandler (message, ^(id result) {
390+ _keyboardChannelResult = result;
391+ });
392+ }
393+
367394- (BOOL )handleTextInputKeyEvent : (NSEvent *)event {
368395 if (_typeStorage != nil && (_typeStorageMask & kTextCall ) != 0 ) {
369396 [_typeStorage addObject: @(kTextCall )];
@@ -382,13 +409,19 @@ - (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {
382409 return LayoutClue{cluePair & kCharMask , (cluePair & kDeadKeyMask ) != 0 };
383410}
384411
412+ - (void )setKeyboardChannelHandler : (NSString *)channel handler : (FlutterBinaryMessageHandler)handler {
413+ _keyboardHandler = handler;
414+ }
415+
385416@end
386417
387418@interface FlutterKeyboardManagerUnittestsObjC : NSObject
388419- (bool )singlePrimaryResponder ;
389420- (bool )doublePrimaryResponder ;
390421- (bool )textInputPlugin ;
391422- (bool )emptyNextResponder ;
423+ - (bool )getPressedState ;
424+ - (bool )keyboardChannelGetPressedState ;
392425- (bool )racingConditionBetweenKeyAndText ;
393426- (bool )correctLogicalKeyForLayouts ;
394427@end
@@ -410,6 +443,14 @@ - (bool)correctLogicalKeyForLayouts;
410443 ASSERT_TRUE ([[FlutterKeyboardManagerUnittestsObjC alloc ] emptyNextResponder ]);
411444}
412445
446+ TEST (FlutterKeyboardManagerUnittests, GetPressedState) {
447+ ASSERT_TRUE ([[FlutterKeyboardManagerUnittestsObjC alloc ] getPressedState ]);
448+ }
449+
450+ TEST (FlutterKeyboardManagerUnittests, KeyboardChannelGetPressedState) {
451+ ASSERT_TRUE ([[FlutterKeyboardManagerUnittestsObjC alloc ] keyboardChannelGetPressedState ]);
452+ }
453+
413454TEST (FlutterKeyboardManagerUnittests, RacingConditionBetweenKeyAndText) {
414455 ASSERT_TRUE ([[FlutterKeyboardManagerUnittestsObjC alloc ] racingConditionBetweenKeyAndText ]);
415456}
@@ -563,6 +604,44 @@ - (bool)emptyNextResponder {
563604 return true ;
564605}
565606
607+ - (bool )getPressedState {
608+ KeyboardTester* tester = [[KeyboardTester alloc ] init ];
609+
610+ [tester respondEmbedderCallsWith: false ];
611+ [tester respondChannelCallsWith: false ];
612+ [tester respondTextInputWith: false ];
613+ [tester.manager handleEvent: keyDownEvent (kVK_ANSI_A )];
614+
615+ NSDictionary * pressingRecords = [tester.manager getPressedState ];
616+ EXPECT_EQ ([pressingRecords count ], 1u );
617+ EXPECT_EQ (pressingRecords[@(kPhysicalKeyA )], @(kLogicalKeyA ));
618+
619+ return true ;
620+ }
621+
622+ - (bool )keyboardChannelGetPressedState {
623+ KeyboardTester* tester = [[KeyboardTester alloc ] init ];
624+
625+ [tester respondEmbedderCallsWith: false ];
626+ [tester respondChannelCallsWith: false ];
627+ [tester respondTextInputWith: false ];
628+ [tester.manager handleEvent: keyDownEvent (kVK_ANSI_A )];
629+
630+ FlutterMethodCall* getKeyboardStateMethodCall =
631+ [FlutterMethodCall methodCallWithMethodName: @" getKeyboardState" arguments: nil ];
632+ NSData * getKeyboardStateMessage =
633+ [[FlutterStandardMethodCodec sharedInstance ] encodeMethodCall: getKeyboardStateMethodCall];
634+ [tester sendKeyboardChannelMessage: getKeyboardStateMessage];
635+
636+ id encodedResult = [tester lastKeyboardChannelResult ];
637+ id decoded = [[FlutterStandardMethodCodec sharedInstance ] decodeEnvelope: encodedResult];
638+
639+ EXPECT_EQ ([decoded count ], 1u );
640+ EXPECT_EQ (decoded[@(kPhysicalKeyA )], @(kLogicalKeyA ));
641+
642+ return true ;
643+ }
644+
566645// Regression test for https://github.com/flutter/flutter/issues/82673.
567646- (bool )racingConditionBetweenKeyAndText {
568647 KeyboardTester* tester = [[KeyboardTester alloc ] init ];
0 commit comments