Flutter macOS Embedder
FlutterKeyboardManagerTest.mm
Go to the documentation of this file.
1 // Copyright 2013 The Flutter Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <Carbon/Carbon.h>
6 #import <Foundation/Foundation.h>
7 #import <OCMock/OCMock.h>
8 
13 #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
14 #import "flutter/testing/testing.h"
15 #include "third_party/googletest/googletest/include/gtest/gtest.h"
16 
17 namespace {
18 
19 using flutter::testing::keycodes::kLogicalBracketLeft;
20 using flutter::testing::keycodes::kLogicalDigit1;
21 using flutter::testing::keycodes::kLogicalDigit2;
22 using flutter::testing::keycodes::kLogicalKeyA;
23 using flutter::testing::keycodes::kLogicalKeyM;
24 using flutter::testing::keycodes::kLogicalKeyQ;
25 using flutter::testing::keycodes::kLogicalKeyT;
26 using flutter::testing::keycodes::kPhysicalKeyA;
27 
29 
30 typedef BOOL (^BoolGetter)();
31 typedef void (^AsyncKeyCallbackHandler)(FlutterAsyncKeyCallback callback);
32 typedef void (^AsyncEmbedderCallbackHandler)(const FlutterKeyEvent* event,
33  FlutterAsyncKeyCallback callback);
34 typedef BOOL (^TextInputCallback)(NSEvent*);
35 
36 // When the Vietnamese IME converts messages into "pure text" messages, their
37 // key codes are set to "empty".
38 //
39 // The 0 also happens to be the key code for key A.
40 constexpr uint16_t kKeyCodeEmpty = 0x00;
41 
42 // Constants used for `recordCallTypesTo:forTypes:`.
43 constexpr uint32_t kEmbedderCall = 0x1;
44 constexpr uint32_t kChannelCall = 0x2;
45 constexpr uint32_t kTextCall = 0x4;
46 
47 // All key clues for a keyboard layout.
48 //
49 // The index is (keyCode * 2 + hasShift). The value is 0xMNNNN, where:
50 //
51 // - M is whether the key is a dead key (0x1 for true, 0x0 for false).
52 // - N is the character for this key. (It only supports UTF-16, but we don't
53 // need full UTF-32 support for unit tests. Moreover, Carbon's UCKeyTranslate
54 // only returns UniChar (UInt16) anyway.)
55 typedef const std::array<uint32_t, 256> MockLayoutData;
56 
57 // The following layout data is generated using DEBUG_PRINT_LAYOUT.
58 
59 MockLayoutData kUsLayout = {
60  // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift
61  /* 0x00 */ 0x00061, 0x00041, 0x00073, 0x00053, 0x00064, 0x00044, 0x00066, 0x00046,
62  /* 0x04 */ 0x00068, 0x00048, 0x00067, 0x00047, 0x0007a, 0x0005a, 0x00078, 0x00058,
63  /* 0x08 */ 0x00063, 0x00043, 0x00076, 0x00056, 0x000a7, 0x000b1, 0x00062, 0x00042,
64  /* 0x0c */ 0x00071, 0x00051, 0x00077, 0x00057, 0x00065, 0x00045, 0x00072, 0x00052,
65  /* 0x10 */ 0x00079, 0x00059, 0x00074, 0x00054, 0x00031, 0x00021, 0x00032, 0x00040,
66  /* 0x14 */ 0x00033, 0x00023, 0x00034, 0x00024, 0x00036, 0x0005e, 0x00035, 0x00025,
67  /* 0x18 */ 0x0003d, 0x0002b, 0x00039, 0x00028, 0x00037, 0x00026, 0x0002d, 0x0005f,
68  /* 0x1c */ 0x00038, 0x0002a, 0x00030, 0x00029, 0x0005d, 0x0007d, 0x0006f, 0x0004f,
69  /* 0x20 */ 0x00075, 0x00055, 0x0005b, 0x0007b, 0x00069, 0x00049, 0x00070, 0x00050,
70  /* 0x24 */ 0x00000, 0x00000, 0x0006c, 0x0004c, 0x0006a, 0x0004a, 0x00027, 0x00022,
71  /* 0x28 */ 0x0006b, 0x0004b, 0x0003b, 0x0003a, 0x0005c, 0x0007c, 0x0002c, 0x0003c,
72  /* 0x2c */ 0x0002f, 0x0003f, 0x0006e, 0x0004e, 0x0006d, 0x0004d, 0x0002e, 0x0003e,
73  /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x00020, 0x00060, 0x0007e,
74 };
75 
76 MockLayoutData kFrenchLayout = {
77  // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift
78  /* 0x00 */ 0x00071, 0x00051, 0x00073, 0x00053, 0x00064, 0x00044, 0x00066, 0x00046,
79  /* 0x04 */ 0x00068, 0x00048, 0x00067, 0x00047, 0x00077, 0x00057, 0x00078, 0x00058,
80  /* 0x08 */ 0x00063, 0x00043, 0x00076, 0x00056, 0x00040, 0x00023, 0x00062, 0x00042,
81  /* 0x0c */ 0x00061, 0x00041, 0x0007a, 0x0005a, 0x00065, 0x00045, 0x00072, 0x00052,
82  /* 0x10 */ 0x00079, 0x00059, 0x00074, 0x00054, 0x00026, 0x00031, 0x000e9, 0x00032,
83  /* 0x14 */ 0x00022, 0x00033, 0x00027, 0x00034, 0x000a7, 0x00036, 0x00028, 0x00035,
84  /* 0x18 */ 0x0002d, 0x0005f, 0x000e7, 0x00039, 0x000e8, 0x00037, 0x00029, 0x000b0,
85  /* 0x1c */ 0x00021, 0x00038, 0x000e0, 0x00030, 0x00024, 0x0002a, 0x0006f, 0x0004f,
86  /* 0x20 */ 0x00075, 0x00055, 0x1005e, 0x100a8, 0x00069, 0x00049, 0x00070, 0x00050,
87  /* 0x24 */ 0x00000, 0x00000, 0x0006c, 0x0004c, 0x0006a, 0x0004a, 0x000f9, 0x00025,
88  /* 0x28 */ 0x0006b, 0x0004b, 0x0006d, 0x0004d, 0x10060, 0x000a3, 0x0003b, 0x0002e,
89  /* 0x2c */ 0x0003d, 0x0002b, 0x0006e, 0x0004e, 0x0002c, 0x0003f, 0x0003a, 0x0002f,
90  /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x00020, 0x0003c, 0x0003e,
91 };
92 
93 MockLayoutData kRussianLayout = {
94  // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift
95  /* 0x00 */ 0x00444, 0x00424, 0x0044b, 0x0042b, 0x00432, 0x00412, 0x00430, 0x00410,
96  /* 0x04 */ 0x00440, 0x00420, 0x0043f, 0x0041f, 0x0044f, 0x0042f, 0x00447, 0x00427,
97  /* 0x08 */ 0x00441, 0x00421, 0x0043c, 0x0041c, 0x0003e, 0x0003c, 0x00438, 0x00418,
98  /* 0x0c */ 0x00439, 0x00419, 0x00446, 0x00426, 0x00443, 0x00423, 0x0043a, 0x0041a,
99  /* 0x10 */ 0x0043d, 0x0041d, 0x00435, 0x00415, 0x00031, 0x00021, 0x00032, 0x00022,
100  /* 0x14 */ 0x00033, 0x02116, 0x00034, 0x00025, 0x00036, 0x0002c, 0x00035, 0x0003a,
101  /* 0x18 */ 0x0003d, 0x0002b, 0x00039, 0x00028, 0x00037, 0x0002e, 0x0002d, 0x0005f,
102  /* 0x1c */ 0x00038, 0x0003b, 0x00030, 0x00029, 0x0044a, 0x0042a, 0x00449, 0x00429,
103  /* 0x20 */ 0x00433, 0x00413, 0x00445, 0x00425, 0x00448, 0x00428, 0x00437, 0x00417,
104  /* 0x24 */ 0x00000, 0x00000, 0x00434, 0x00414, 0x0043e, 0x0041e, 0x0044d, 0x0042d,
105  /* 0x28 */ 0x0043b, 0x0041b, 0x00436, 0x00416, 0x00451, 0x00401, 0x00431, 0x00411,
106  /* 0x2c */ 0x0002f, 0x0003f, 0x00442, 0x00422, 0x0044c, 0x0042c, 0x0044e, 0x0042e,
107  /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x00020, 0x0005d, 0x0005b,
108 };
109 
110 MockLayoutData kKhmerLayout = {
111  // +0x0 Shift +0x1 Shift +0x2 Shift +0x3 Shift
112  /* 0x00 */ 0x017b6, 0x017ab, 0x0179f, 0x017c3, 0x0178a, 0x0178c, 0x01790, 0x01792,
113  /* 0x04 */ 0x017a0, 0x017c7, 0x01784, 0x017a2, 0x0178b, 0x0178d, 0x01781, 0x01783,
114  /* 0x08 */ 0x01785, 0x01787, 0x0179c, 0x017c8, 0x00000, 0x00000, 0x01794, 0x01796,
115  /* 0x0c */ 0x01786, 0x01788, 0x017b9, 0x017ba, 0x017c1, 0x017c2, 0x0179a, 0x017ac,
116  /* 0x10 */ 0x01799, 0x017bd, 0x0178f, 0x01791, 0x017e1, 0x00021, 0x017e2, 0x017d7,
117  /* 0x14 */ 0x017e3, 0x00022, 0x017e4, 0x017db, 0x017e6, 0x017cd, 0x017e5, 0x00025,
118  /* 0x18 */ 0x017b2, 0x017ce, 0x017e9, 0x017b0, 0x017e7, 0x017d0, 0x017a5, 0x017cc,
119  /* 0x1c */ 0x017e8, 0x017cf, 0x017e0, 0x017b3, 0x017aa, 0x017a7, 0x017c4, 0x017c5,
120  /* 0x20 */ 0x017bb, 0x017bc, 0x017c0, 0x017bf, 0x017b7, 0x017b8, 0x01795, 0x01797,
121  /* 0x24 */ 0x00000, 0x00000, 0x0179b, 0x017a1, 0x017d2, 0x01789, 0x017cb, 0x017c9,
122  /* 0x28 */ 0x01780, 0x01782, 0x017be, 0x017d6, 0x017ad, 0x017ae, 0x017a6, 0x017b1,
123  /* 0x2c */ 0x017ca, 0x017af, 0x01793, 0x0178e, 0x01798, 0x017c6, 0x017d4, 0x017d5,
124  /* 0x30 */ 0x00000, 0x00000, 0x00020, 0x0200b, 0x000ab, 0x000bb,
125 };
126 
127 NSEvent* keyDownEvent(unsigned short keyCode, NSString* chars = @"", NSString* charsUnmod = @"") {
128  return [NSEvent keyEventWithType:NSEventTypeKeyDown
129  location:NSZeroPoint
130  modifierFlags:0x100
131  timestamp:0
132  windowNumber:0
133  context:nil
134  characters:chars
135  charactersIgnoringModifiers:charsUnmod
136  isARepeat:NO
137  keyCode:keyCode];
138 }
139 
140 NSEvent* keyUpEvent(unsigned short keyCode) {
141  return [NSEvent keyEventWithType:NSEventTypeKeyUp
142  location:NSZeroPoint
143  modifierFlags:0x100
144  timestamp:0
145  windowNumber:0
146  context:nil
147  characters:@""
148  charactersIgnoringModifiers:@""
149  isARepeat:NO
150  keyCode:keyCode];
151 }
152 
153 id checkKeyDownEvent(unsigned short keyCode) {
154  return [OCMArg checkWithBlock:^BOOL(id value) {
155  if (![value isKindOfClass:[NSEvent class]]) {
156  return NO;
157  }
158  NSEvent* event = value;
159  return event.keyCode == keyCode;
160  }];
161 }
162 
163 // Clear a list of `FlutterKeyEvent` whose `character` is dynamically allocated.
164 void clearEvents(std::vector<FlutterKeyEvent>& events) {
165  for (FlutterKeyEvent& event : events) {
166  if (event.character != nullptr) {
167  delete[] event.character;
168  }
169  }
170  events.clear();
171 }
172 
173 #define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR) \
174  EXPECT_EQ(events[0].type, kFlutterKeyEventTypeDown); \
175  EXPECT_EQ(events[0].logical, static_cast<uint64_t>(OUT_LOGICAL)); \
176  EXPECT_STREQ(events[0].character, (OUT_CHAR)); \
177  clearEvents(events);
178 
179 } // namespace
180 
181 @interface KeyboardTester : NSObject
182 - (nonnull instancetype)init;
183 
184 // Set embedder calls to respond immediately with the given response.
185 - (void)respondEmbedderCallsWith:(BOOL)response;
186 
187 // Record embedder calls to the given storage.
188 //
189 // They are not responded to until the stored callbacks are manually called.
190 - (void)recordEmbedderCallsTo:(nonnull NSMutableArray<FlutterAsyncKeyCallback>*)storage;
191 
192 - (void)recordEmbedderEventsTo:(nonnull std::vector<FlutterKeyEvent>*)storage
193  returning:(bool)handled;
194 
195 // Set channel calls to respond immediately with the given response.
196 - (void)respondChannelCallsWith:(BOOL)response;
197 
198 // Record channel calls to the given storage.
199 //
200 // They are not responded to until the stored callbacks are manually called.
201 - (void)recordChannelCallsTo:(nonnull NSMutableArray<FlutterAsyncKeyCallback>*)storage;
202 
203 // Set text calls to respond with the given response.
204 - (void)respondTextInputWith:(BOOL)response;
205 
206 // At the start of any kind of call, record the call type to the given storage.
207 //
208 // Only calls that are included in `typeMask` will be added. Options are
209 // kEmbedderCall, kChannelCall, and kTextCall.
210 //
211 // This method does not conflict with other call settings, and the recording
212 // takes place before the callbacks are (or are not) invoked.
213 - (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
214  forTypes:(uint32_t)typeMask;
215 
217 
218 - (void)sendKeyboardChannelMessage:(NSData* _Nullable)message;
219 
220 @property(readonly, nonatomic, strong) FlutterKeyboardManager* manager;
221 @property(readonly, nonatomic, strong) id<FlutterKeyboardManagerEventContext> eventContextMock;
222 @property(nonatomic, nullable, strong) NSResponder* nextResponder;
223 
224 #pragma mark - Private
225 
226 - (void)handleEmbedderEvent:(const FlutterKeyEvent&)event
227  callback:(nullable FlutterKeyEventCallback)callback
228  userData:(nullable void*)userData;
229 
230 - (void)handleChannelMessage:(NSString*)channel
231  message:(NSData* _Nullable)message
232  binaryReply:(FlutterBinaryReply _Nullable)callback;
233 
234 - (BOOL)handleTextInputKeyEvent:(NSEvent*)event;
235 @end
236 
237 @implementation KeyboardTester {
238  AsyncEmbedderCallbackHandler _embedderHandler;
239  AsyncKeyCallbackHandler _channelHandler;
240  TextInputCallback _textCallback;
241 
242  NSMutableArray<NSNumber*>* _typeStorage;
244 
245  __weak id<FlutterKeyboardLayoutDelegate> _keyboardLayoutDelegate;
246  const MockLayoutData* _currentLayout;
247 
249  NSObject<FlutterBinaryMessenger>* _messengerMock;
251 
252  NSObject<FlutterKeyboardManagerEventContext>* _eventContextMock;
253 }
254 
255 - (nonnull instancetype)init {
256  self = [super init];
257  if (self == nil) {
258  return nil;
259  }
260 
261  _nextResponder = OCMClassMock([NSResponder class]);
262  [self respondChannelCallsWith:FALSE];
263  [self respondEmbedderCallsWith:FALSE];
264  [self respondTextInputWith:FALSE];
265 
266  _currentLayout = &kUsLayout;
267 
268  _messengerMock = OCMStrictProtocolMock(@protocol(FlutterBinaryMessenger));
269  OCMStub([_messengerMock sendOnChannel:@"flutter/keyevent"
270  message:[OCMArg any]
271  binaryReply:[OCMArg any]])
272  .andCall(self, @selector(handleChannelMessage:message:binaryReply:));
273  OCMStub([_messengerMock setMessageHandlerOnChannel:@"flutter/keyboard"
274  binaryMessageHandler:[OCMArg any]])
275  .andCall(self, @selector(setKeyboardChannelHandler:handler:));
276  OCMStub([_messengerMock sendOnChannel:@"flutter/keyboard" message:[OCMArg any]])
277  .andCall(self, @selector(handleKeyboardChannelMessage:message:));
278  id managerDelegateMock = OCMStrictProtocolMock(@protocol(FlutterKeyboardManagerDelegate));
279  OCMStub([managerDelegateMock binaryMessenger]).andReturn(_messengerMock);
280  OCMStub([managerDelegateMock sendKeyEvent:*(const FlutterKeyEvent*)[OCMArg anyPointer]
281  callback:nil
282  userData:nil])
283  .ignoringNonObjectArgs()
284  .andCall(self, @selector(handleEmbedderEvent:callback:userData:));
285 
286  _eventContextMock = OCMStrictProtocolMock(@protocol(FlutterKeyboardManagerEventContext));
287  OCMStub([_eventContextMock nextResponder]).andReturn(_nextResponder);
288  OCMStub([_eventContextMock onTextInputKeyEvent:[OCMArg any]])
289  .andCall(self, @selector(handleTextInputKeyEvent:));
290 
291  id keyboardLayoutMock = OCMStrictClassMock([FlutterKeyboardLayout class]);
292  OCMStub([keyboardLayoutMock lookUpLayoutForKeyCode:0 shift:false])
293  .ignoringNonObjectArgs()
294  .andCall(self, @selector(lookUpLayoutForKeyCode:shift:));
295  OCMStub([keyboardLayoutMock setDelegate:[OCMArg any]])
296  .andCall(self, @selector(onSetKeyboardLayoutDelegate:));
297 
298  _manager = [[FlutterKeyboardManager alloc] initWithDelegate:managerDelegateMock
299  keyboardLayout:keyboardLayoutMock];
300  return self;
301 }
302 
303 - (id)lastKeyboardChannelResult {
304  return _keyboardChannelResult;
305 }
306 
307 - (void)respondEmbedderCallsWith:(BOOL)response {
308  _embedderHandler = ^(const FlutterKeyEvent* event, FlutterAsyncKeyCallback callback) {
309  callback(response);
310  };
311 }
312 
313 - (void)recordEmbedderCallsTo:(nonnull NSMutableArray<FlutterAsyncKeyCallback>*)storage {
314  _embedderHandler = ^(const FlutterKeyEvent* event, FlutterAsyncKeyCallback callback) {
315  [storage addObject:callback];
316  };
317 }
318 
319 - (void)recordEmbedderEventsTo:(nonnull std::vector<FlutterKeyEvent>*)storage
320  returning:(bool)handled {
321  _embedderHandler = ^(const FlutterKeyEvent* event, FlutterAsyncKeyCallback callback) {
322  FlutterKeyEvent newEvent = *event;
323  if (event->character != nullptr) {
324  size_t charLen = strlen(event->character);
325  char* newCharacter = new char[charLen + 1];
326  strlcpy(newCharacter, event->character, charLen + 1);
327  newEvent.character = newCharacter;
328  }
329  storage->push_back(newEvent);
330  callback(handled);
331  };
332 }
333 
334 - (void)respondChannelCallsWith:(BOOL)response {
336  callback(response);
337  };
338 }
339 
340 - (void)recordChannelCallsTo:(nonnull NSMutableArray<FlutterAsyncKeyCallback>*)storage {
342  [storage addObject:callback];
343  };
344 }
345 
346 - (void)respondTextInputWith:(BOOL)response {
347  _textCallback = ^(NSEvent* event) {
348  return response;
349  };
350 }
351 
352 - (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
353  forTypes:(uint32_t)typeMask {
354  _typeStorage = typeStorage;
355  _typeStorageMask = typeMask;
356 }
357 
358 - (void)sendKeyboardChannelMessage:(NSData* _Nullable)message {
359  [_messengerMock sendOnChannel:@"flutter/keyboard" message:message];
360 }
361 
362 - (void)setLayout:(const MockLayoutData&)layout {
363  _currentLayout = &layout;
364  [_keyboardLayoutDelegate keyboardLayoutDidChange];
365 }
366 
367 #pragma mark - Private
368 
369 - (void)handleEmbedderEvent:(const FlutterKeyEvent&)event
370  callback:(nullable FlutterKeyEventCallback)callback
371  userData:(nullable void*)userData {
372  if (_typeStorage != nil && (_typeStorageMask & kEmbedderCall) != 0) {
373  [_typeStorage addObject:@(kEmbedderCall)];
374  }
375  if (callback != nullptr) {
376  _embedderHandler(&event, ^(BOOL handled) {
377  callback(handled, userData);
378  });
379  }
380 }
381 
382 - (void)handleChannelMessage:(NSString*)channel
383  message:(NSData* _Nullable)message
384  binaryReply:(FlutterBinaryReply _Nullable)callback {
385  if (_typeStorage != nil && (_typeStorageMask & kChannelCall) != 0) {
386  [_typeStorage addObject:@(kChannelCall)];
387  }
388  _channelHandler(^(BOOL handled) {
389  NSDictionary* result = @{
390  @"handled" : @(handled),
391  };
392  NSData* encodedKeyEvent = [[FlutterJSONMessageCodec sharedInstance] encode:result];
393  callback(encodedKeyEvent);
394  });
395 }
396 
397 - (void)handleKeyboardChannelMessage:(NSString*)channel message:(NSData* _Nullable)message {
398  _keyboardHandler(message, ^(id result) {
399  _keyboardChannelResult = result;
400  });
401 }
402 
403 - (BOOL)handleTextInputKeyEvent:(NSEvent*)event {
404  if (_typeStorage != nil && (_typeStorageMask & kTextCall) != 0) {
405  [_typeStorage addObject:@(kTextCall)];
406  }
407  return _textCallback(event);
408 }
409 
410 - (void)onSetKeyboardLayoutDelegate:(id<FlutterKeyboardLayoutDelegate>)delegate {
411  _keyboardLayoutDelegate = delegate;
412 }
413 
414 - (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {
415  uint32_t cluePair = (*_currentLayout)[(keyCode * 2) + (shift ? 1 : 0)];
416  const uint32_t kCharMask = 0xffff;
417  const uint32_t kDeadKeyMask = 0x10000;
418  return LayoutClue{cluePair & kCharMask, (cluePair & kDeadKeyMask) != 0};
419 }
420 
421 - (void)setKeyboardChannelHandler:(NSString*)channel handler:(FlutterBinaryMessageHandler)handler {
422  _keyboardHandler = handler;
423 }
424 
425 @end
426 
428 - (bool)singlePrimaryResponder;
429 - (bool)doublePrimaryResponder;
430 - (bool)textInputPlugin;
431 - (bool)emptyNextResponder;
432 - (bool)getPressedState;
437 @end
438 
439 namespace flutter::testing {
440 
441 TEST(FlutterKeyboardManagerUnittests, SinglePrimaryResponder) {
442  ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] singlePrimaryResponder]);
443 }
444 
445 TEST(FlutterKeyboardManagerUnittests, DoublePrimaryResponder) {
446  ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] doublePrimaryResponder]);
447 }
448 
449 TEST(FlutterKeyboardManagerUnittests, SingleFinalResponder) {
450  ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] textInputPlugin]);
451 }
452 
453 TEST(FlutterKeyboardManagerUnittests, EmptyNextResponder) {
454  ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] emptyNextResponder]);
455 }
456 
457 TEST(FlutterKeyboardManagerUnittests, GetPressedState) {
458  ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] getPressedState]);
459 }
460 
461 TEST(FlutterKeyboardManagerUnittests, KeyboardChannelGetPressedState) {
462  ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] keyboardChannelGetPressedState]);
463 }
464 
465 TEST(FlutterKeyboardManagerUnittests, RacingConditionBetweenKeyAndText) {
466  ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] racingConditionBetweenKeyAndText]);
467 }
468 
469 TEST(FlutterKeyboardManagerUnittests, CorrectLogicalKeyForLayouts) {
470  ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] correctLogicalKeyForLayouts]);
471 }
472 
473 TEST(FlutterKeyboardManagerUnittests, ShouldNotHoldStrongReferenceToDelegate) {
474  ASSERT_TRUE([[FlutterKeyboardManagerUnittestsObjC alloc] shouldNotHoldStrongReferenceToDelegate]);
475 }
476 
477 } // namespace flutter::testing
478 
480 
482  KeyboardTester* tester = [[KeyboardTester alloc] init];
483  NSMutableArray<FlutterAsyncKeyCallback>* embedderCallbacks =
484  [NSMutableArray<FlutterAsyncKeyCallback> array];
485  [tester recordEmbedderCallsTo:embedderCallbacks];
486 
487  // Case: The responder reports FALSE
488  [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
489  EXPECT_EQ([embedderCallbacks count], 1u);
490  embedderCallbacks[0](FALSE);
491  OCMVerify([tester.nextResponder keyDown:checkKeyDownEvent(0x50)]);
492  [embedderCallbacks removeAllObjects];
493 
494  // Case: The responder reports TRUE
495  [tester.manager handleEvent:keyUpEvent(0x50) withContext:tester.eventContextMock];
496  EXPECT_EQ([embedderCallbacks count], 1u);
497  embedderCallbacks[0](TRUE);
498  // [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown.
499 
500  return true;
501 }
502 
504  KeyboardTester* tester = [[KeyboardTester alloc] init];
505 
506  // Send a down event first so we can send an up event later.
507  [tester respondEmbedderCallsWith:false];
508  [tester respondChannelCallsWith:false];
509  [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
510 
511  NSMutableArray<FlutterAsyncKeyCallback>* embedderCallbacks =
512  [NSMutableArray<FlutterAsyncKeyCallback> array];
513  NSMutableArray<FlutterAsyncKeyCallback>* channelCallbacks =
514  [NSMutableArray<FlutterAsyncKeyCallback> array];
515  [tester recordEmbedderCallsTo:embedderCallbacks];
516  [tester recordChannelCallsTo:channelCallbacks];
517 
518  // Case: Both responders report TRUE.
519  [tester.manager handleEvent:keyUpEvent(0x50) withContext:tester.eventContextMock];
520  EXPECT_EQ([embedderCallbacks count], 1u);
521  EXPECT_EQ([channelCallbacks count], 1u);
522  embedderCallbacks[0](TRUE);
523  channelCallbacks[0](TRUE);
524  EXPECT_EQ([embedderCallbacks count], 1u);
525  EXPECT_EQ([channelCallbacks count], 1u);
526  // [tester.nextResponder keyUp:] should not be called, otherwise an error will be thrown.
527  [embedderCallbacks removeAllObjects];
528  [channelCallbacks removeAllObjects];
529 
530  // Case: One responder reports TRUE.
531  [tester respondEmbedderCallsWith:false];
532  [tester respondChannelCallsWith:false];
533  [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
534 
535  [tester recordEmbedderCallsTo:embedderCallbacks];
536  [tester recordChannelCallsTo:channelCallbacks];
537  [tester.manager handleEvent:keyUpEvent(0x50) withContext:tester.eventContextMock];
538  EXPECT_EQ([embedderCallbacks count], 1u);
539  EXPECT_EQ([channelCallbacks count], 1u);
540  embedderCallbacks[0](FALSE);
541  channelCallbacks[0](TRUE);
542  EXPECT_EQ([embedderCallbacks count], 1u);
543  EXPECT_EQ([channelCallbacks count], 1u);
544  // [tester.nextResponder keyUp:] should not be called, otherwise an error will be thrown.
545  [embedderCallbacks removeAllObjects];
546  [channelCallbacks removeAllObjects];
547 
548  // Case: Both responders report FALSE.
549  [tester.manager handleEvent:keyDownEvent(0x53) withContext:tester.eventContextMock];
550  EXPECT_EQ([embedderCallbacks count], 1u);
551  EXPECT_EQ([channelCallbacks count], 1u);
552  embedderCallbacks[0](FALSE);
553  channelCallbacks[0](FALSE);
554  EXPECT_EQ([embedderCallbacks count], 1u);
555  EXPECT_EQ([channelCallbacks count], 1u);
556  OCMVerify([tester.nextResponder keyDown:checkKeyDownEvent(0x53)]);
557  [embedderCallbacks removeAllObjects];
558  [channelCallbacks removeAllObjects];
559 
560  return true;
561 }
562 
564  KeyboardTester* tester = [[KeyboardTester alloc] init];
565 
566  // Send a down event first so we can send an up event later.
567  [tester respondEmbedderCallsWith:false];
568  [tester respondChannelCallsWith:false];
569  [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
570 
571  NSMutableArray<FlutterAsyncKeyCallback>* callbacks =
572  [NSMutableArray<FlutterAsyncKeyCallback> array];
573  [tester recordEmbedderCallsTo:callbacks];
574 
575  // Case: Primary responder responds TRUE. The event shouldn't be handled by
576  // the secondary responder.
577  [tester respondTextInputWith:FALSE];
578  [tester.manager handleEvent:keyUpEvent(0x50) withContext:tester.eventContextMock];
579  EXPECT_EQ([callbacks count], 1u);
580  callbacks[0](TRUE);
581  // [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown.
582  [callbacks removeAllObjects];
583 
584  // Send a down event first so we can send an up event later.
585  [tester respondEmbedderCallsWith:false];
586  [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
587 
588  // Case: Primary responder responds FALSE. The secondary responder returns
589  // TRUE.
590  [tester recordEmbedderCallsTo:callbacks];
591  [tester respondTextInputWith:TRUE];
592  [tester.manager handleEvent:keyUpEvent(0x50) withContext:tester.eventContextMock];
593  EXPECT_EQ([callbacks count], 1u);
594  callbacks[0](FALSE);
595  // [owner.nextResponder keyUp:] should not be called, otherwise an error will be thrown.
596  [callbacks removeAllObjects];
597 
598  // Case: Primary responder responds FALSE. The secondary responder returns FALSE.
599  [tester respondTextInputWith:FALSE];
600  [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
601  EXPECT_EQ([callbacks count], 1u);
602  callbacks[0](FALSE);
603  OCMVerify([tester.nextResponder keyDown:checkKeyDownEvent(0x50)]);
604  [callbacks removeAllObjects];
605 
606  return true;
607 }
608 
610  KeyboardTester* tester = [[KeyboardTester alloc] init];
611  tester.nextResponder = nil;
612 
613  [tester respondEmbedderCallsWith:false];
614  [tester respondChannelCallsWith:false];
615  [tester respondTextInputWith:false];
616  [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
617 
618  // Passes if no error is thrown.
619  return true;
620 }
621 
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) withContext:tester.eventContextMock];
629 
630  NSDictionary* pressingRecords = [tester.manager getPressedState];
631  EXPECT_EQ([pressingRecords count], 1u);
632  EXPECT_EQ(pressingRecords[@(kPhysicalKeyA)], @(kLogicalKeyA));
633 
634  return true;
635 }
636 
638  KeyboardTester* tester = [[KeyboardTester alloc] init];
639 
640  [tester respondEmbedderCallsWith:false];
641  [tester respondChannelCallsWith:false];
642  [tester respondTextInputWith:false];
643  [tester.manager handleEvent:keyDownEvent(kVK_ANSI_A) withContext:tester.eventContextMock];
644 
645  FlutterMethodCall* getKeyboardStateMethodCall =
646  [FlutterMethodCall methodCallWithMethodName:@"getKeyboardState" arguments:nil];
647  NSData* getKeyboardStateMessage =
648  [[FlutterStandardMethodCodec sharedInstance] encodeMethodCall:getKeyboardStateMethodCall];
649  [tester sendKeyboardChannelMessage:getKeyboardStateMessage];
650 
651  id encodedResult = [tester lastKeyboardChannelResult];
652  id decoded = [[FlutterStandardMethodCodec sharedInstance] decodeEnvelope:encodedResult];
653 
654  EXPECT_EQ([decoded count], 1u);
655  EXPECT_EQ(decoded[@(kPhysicalKeyA)], @(kLogicalKeyA));
656 
657  return true;
658 }
659 
660 // Regression test for https://github.com/flutter/flutter/issues/82673.
662  KeyboardTester* tester = [[KeyboardTester alloc] init];
663 
664  // Use Vietnamese IME (GoTiengViet, Telex mode) to type "uco".
665 
666  // The events received by the framework. The engine might receive
667  // a channel message "setEditingState" from the framework.
668  NSMutableArray<FlutterAsyncKeyCallback>* keyCallbacks =
669  [NSMutableArray<FlutterAsyncKeyCallback> array];
670  [tester recordEmbedderCallsTo:keyCallbacks];
671 
672  NSMutableArray<NSNumber*>* allCalls = [NSMutableArray<NSNumber*> array];
673  [tester recordCallTypesTo:allCalls forTypes:(kEmbedderCall | kTextCall)];
674 
675  // Tap key U, which is converted by IME into a pure text message "Æ°".
676 
677  [tester.manager handleEvent:keyDownEvent(kKeyCodeEmpty, @"Æ°", @"Æ°")
678  withContext:tester.eventContextMock];
679  EXPECT_EQ([keyCallbacks count], 1u);
680  EXPECT_EQ([allCalls count], 1u);
681  EXPECT_EQ(allCalls[0], @(kEmbedderCall));
682  keyCallbacks[0](false);
683  EXPECT_EQ([keyCallbacks count], 1u);
684  EXPECT_EQ([allCalls count], 2u);
685  EXPECT_EQ(allCalls[1], @(kTextCall));
686  [keyCallbacks removeAllObjects];
687  [allCalls removeAllObjects];
688 
689  [tester.manager handleEvent:keyUpEvent(kKeyCodeEmpty) withContext:tester.eventContextMock];
690  EXPECT_EQ([keyCallbacks count], 1u);
691  keyCallbacks[0](false);
692  EXPECT_EQ([keyCallbacks count], 1u);
693  EXPECT_EQ([allCalls count], 2u);
694  [keyCallbacks removeAllObjects];
695  [allCalls removeAllObjects];
696 
697  // Tap key O, which is converted to normal KeyO events, but the responses are
698  // slow.
699 
700  [tester.manager handleEvent:keyDownEvent(kVK_ANSI_O, @"o", @"o")
701  withContext:tester.eventContextMock];
702  [tester.manager handleEvent:keyUpEvent(kVK_ANSI_O) withContext:tester.eventContextMock];
703  EXPECT_EQ([keyCallbacks count], 1u);
704  EXPECT_EQ([allCalls count], 1u);
705  EXPECT_EQ(allCalls[0], @(kEmbedderCall));
706 
707  // Tap key C, which results in two Backspace messages first - and here they
708  // arrive before the key O messages are responded.
709 
710  [tester.manager handleEvent:keyDownEvent(kVK_Delete) withContext:tester.eventContextMock];
711  [tester.manager handleEvent:keyUpEvent(kVK_Delete) withContext:tester.eventContextMock];
712  EXPECT_EQ([keyCallbacks count], 1u);
713  EXPECT_EQ([allCalls count], 1u);
714 
715  // The key O down is responded, which releases a text call (for KeyO down) and
716  // an embedder call (for KeyO up) immediately.
717  keyCallbacks[0](false);
718  EXPECT_EQ([keyCallbacks count], 2u);
719  EXPECT_EQ([allCalls count], 3u);
720  EXPECT_EQ(allCalls[1], @(kTextCall)); // The order is important!
721  EXPECT_EQ(allCalls[2], @(kEmbedderCall));
722 
723  // The key O up is responded, which releases a text call (for KeyO up) and
724  // an embedder call (for Backspace down) immediately.
725  keyCallbacks[1](false);
726  EXPECT_EQ([keyCallbacks count], 3u);
727  EXPECT_EQ([allCalls count], 5u);
728  EXPECT_EQ(allCalls[3], @(kTextCall)); // The order is important!
729  EXPECT_EQ(allCalls[4], @(kEmbedderCall));
730 
731  // Finish up callbacks.
732  keyCallbacks[2](false);
733  keyCallbacks[3](false);
734 
735  return true;
736 }
737 
739  KeyboardTester* tester = [[KeyboardTester alloc] init];
740  tester.nextResponder = nil;
741 
742  std::vector<FlutterKeyEvent> events;
743  [tester recordEmbedderEventsTo:&events returning:true];
744  [tester respondChannelCallsWith:false];
745  [tester respondTextInputWith:false];
746 
747  auto sendTap = [&](uint16_t keyCode, NSString* chars, NSString* charsUnmod) {
748  [tester.manager handleEvent:keyDownEvent(keyCode, chars, charsUnmod)
749  withContext:tester.eventContextMock];
750  [tester.manager handleEvent:keyUpEvent(keyCode) withContext:tester.eventContextMock];
751  };
752 
753  /* US keyboard layout */
754 
755  sendTap(kVK_ANSI_A, @"a", @"a"); // KeyA
756  VERIFY_DOWN(kLogicalKeyA, "a");
757 
758  sendTap(kVK_ANSI_A, @"A", @"A"); // Shift-KeyA
759  VERIFY_DOWN(kLogicalKeyA, "A");
760 
761  sendTap(kVK_ANSI_A, @"Ã¥", @"a"); // Option-KeyA
762  VERIFY_DOWN(kLogicalKeyA, "Ã¥");
763 
764  sendTap(kVK_ANSI_T, @"t", @"t"); // KeyT
765  VERIFY_DOWN(kLogicalKeyT, "t");
766 
767  sendTap(kVK_ANSI_1, @"1", @"1"); // Digit1
768  VERIFY_DOWN(kLogicalDigit1, "1");
769 
770  sendTap(kVK_ANSI_1, @"!", @"!"); // Shift-Digit1
771  VERIFY_DOWN(kLogicalDigit1, "!");
772 
773  sendTap(kVK_ANSI_Minus, @"-", @"-"); // Minus
774  VERIFY_DOWN('-', "-");
775 
776  sendTap(kVK_ANSI_Minus, @"=", @"="); // Shift-Minus
777  VERIFY_DOWN('=', "=");
778 
779  /* French keyboard layout */
780  [tester setLayout:kFrenchLayout];
781 
782  sendTap(kVK_ANSI_A, @"q", @"q"); // KeyA
783  VERIFY_DOWN(kLogicalKeyQ, "q");
784 
785  sendTap(kVK_ANSI_A, @"Q", @"Q"); // Shift-KeyA
786  VERIFY_DOWN(kLogicalKeyQ, "Q");
787 
788  sendTap(kVK_ANSI_Semicolon, @"m", @"m"); // ; but prints M
789  VERIFY_DOWN(kLogicalKeyM, "m");
790 
791  sendTap(kVK_ANSI_M, @",", @","); // M but prints ,
792  VERIFY_DOWN(',', ",");
793 
794  sendTap(kVK_ANSI_1, @"&", @"&"); // Digit1
795  VERIFY_DOWN(kLogicalDigit1, "&");
796 
797  sendTap(kVK_ANSI_1, @"1", @"1"); // Shift-Digit1
798  VERIFY_DOWN(kLogicalDigit1, "1");
799 
800  sendTap(kVK_ANSI_Minus, @")", @")"); // Minus
801  VERIFY_DOWN(')', ")");
802 
803  sendTap(kVK_ANSI_Minus, @"°", @"°"); // Shift-Minus
804  VERIFY_DOWN(L'°', "°");
805 
806  /* Russian keyboard layout */
807  [tester setLayout:kRussianLayout];
808 
809  sendTap(kVK_ANSI_A, @"Ñ„", @"Ñ„"); // KeyA
810  VERIFY_DOWN(kLogicalKeyA, "Ñ„");
811 
812  sendTap(kVK_ANSI_1, @"1", @"1"); // Digit1
813  VERIFY_DOWN(kLogicalDigit1, "1");
814 
815  sendTap(kVK_ANSI_LeftBracket, @"Ñ…", @"Ñ…");
816  VERIFY_DOWN(kLogicalBracketLeft, "Ñ…");
817 
818  /* Khmer keyboard layout */
819  // Regression test for https://github.com/flutter/flutter/issues/108729
820  [tester setLayout:kKhmerLayout];
821 
822  sendTap(kVK_ANSI_2, @"២", @"២"); // Digit2
823  VERIFY_DOWN(kLogicalDigit2, "២");
824 
825  return TRUE;
826 }
827 
829  __strong FlutterKeyboardManager* strongKeyboardManager;
830  __weak id weakDelegate;
831 
832  @autoreleasepool {
833  id binaryMessengerMock = OCMStrictProtocolMock(@protocol(FlutterBinaryMessenger));
834  OCMStub([binaryMessengerMock setMessageHandlerOnChannel:[OCMArg any]
835  binaryMessageHandler:[OCMArg any]]);
836 
837  id delegateMock = OCMStrictProtocolMock(@protocol(FlutterKeyboardManagerDelegate));
838  OCMStub([delegateMock binaryMessenger]).andReturn(binaryMessengerMock);
839 
840  FlutterKeyboardManager* keyboardManager =
841  [[FlutterKeyboardManager alloc] initWithDelegate:delegateMock];
842  strongKeyboardManager = keyboardManager;
843  weakDelegate = delegateMock;
844  }
845 
846  return weakDelegate == nil;
847 }
848 
849 @end
FlutterKeyboardManagerEventContext-p
Definition: FlutterKeyboardManager.h:40
flutter::LayoutClue
Definition: FlutterKeyboardLayout.h:14
-[KeyboardTester recordEmbedderCallsTo:]
void recordEmbedderCallsTo:(nonnull NSMutableArray< FlutterAsyncKeyCallback > *storage)
Definition: FlutterKeyboardManagerTest.mm:313
-[KeyboardTester recordChannelCallsTo:]
void recordChannelCallsTo:(nonnull NSMutableArray< FlutterAsyncKeyCallback > *storage)
Definition: FlutterKeyboardManagerTest.mm:340
-[FlutterKeyboardManagerUnittestsObjC correctLogicalKeyForLayouts]
bool correctLogicalKeyForLayouts()
Definition: FlutterKeyboardManagerTest.mm:738
+[FlutterMethodCall methodCallWithMethodName:arguments:]
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
FlutterKeyboardLayoutDelegate-p
Definition: FlutterKeyboardLayout.h:33
FlutterKeyboardLayout.h
FlutterEngine_Internal.h
-[KeyboardTester respondTextInputWith:]
void respondTextInputWith:(BOOL response)
Definition: FlutterKeyboardManagerTest.mm:346
-[KeyboardTester recordEmbedderEventsTo:returning:]
void recordEmbedderEventsTo:returning:(nonnull std::vector< FlutterKeyEvent > *storage,[returning] bool handled)
Definition: FlutterKeyboardManagerTest.mm:319
flutter::testing
Definition: AccessibilityBridgeMacTest.mm:13
flutter::testing::TEST
TEST(FlutterAppDelegateTest, DoesNotCallDelegatesWithoutHandler)
Definition: FlutterAppDelegateTest.mm:32
flutter::LayoutClue::character
uint32_t character
Definition: FlutterKeyboardLayout.h:19
-[FlutterKeyboardManagerUnittestsObjC getPressedState]
bool getPressedState()
Definition: FlutterKeyboardManagerTest.mm:622
_keyboardLayoutDelegate
__weak id< FlutterKeyboardLayoutDelegate > _keyboardLayoutDelegate
Definition: FlutterKeyboardManagerTest.mm:245
-[KeyboardTester init]
nonnull instancetype init()
Definition: FlutterKeyboardManagerTest.mm:255
FlutterKeyPrimaryResponder.h
FlutterBinaryMessageHandler
void(^ FlutterBinaryMessageHandler)(NSData *_Nullable message, FlutterBinaryReply reply)
Definition: FlutterBinaryMessenger.h:30
FlutterKeyboardManagerDelegate-p
Definition: FlutterKeyboardManager.h:13
-[KeyboardTester recordCallTypesTo:forTypes:]
void recordCallTypesTo:forTypes:(nonnull NSMutableArray< NSNumber * > *typeStorage,[forTypes] uint32_t typeMask)
Definition: FlutterKeyboardManagerTest.mm:352
-[FlutterKeyboardManagerUnittestsObjC racingConditionBetweenKeyAndText]
bool racingConditionBetweenKeyAndText()
Definition: FlutterKeyboardManagerTest.mm:661
-[KeyboardTester respondEmbedderCallsWith:]
void respondEmbedderCallsWith:(BOOL response)
Definition: FlutterKeyboardManagerTest.mm:307
_channelHandler
AsyncKeyCallbackHandler _channelHandler
Definition: FlutterKeyboardManagerTest.mm:237
KeyboardTester::eventContextMock
id< FlutterKeyboardManagerEventContext > eventContextMock
Definition: FlutterKeyboardManagerTest.mm:221
KeyboardTester::nextResponder
NSResponder * nextResponder
Definition: FlutterKeyboardManagerTest.mm:222
FlutterMethodCall
Definition: FlutterCodecs.h:220
-[FlutterKeyboardManagerUnittestsObjC emptyNextResponder]
bool emptyNextResponder()
Definition: FlutterKeyboardManagerTest.mm:609
FlutterAsyncKeyCallback
void(^ FlutterAsyncKeyCallback)(BOOL handled)
Definition: FlutterKeyPrimaryResponder.h:10
-[FlutterKeyboardManagerUnittestsObjC keyboardChannelGetPressedState]
bool keyboardChannelGetPressedState()
Definition: FlutterKeyboardManagerTest.mm:637
_typeStorageMask
uint32_t _typeStorageMask
Definition: FlutterKeyboardManagerTest.mm:243
-[FlutterKeyboardManagerUnittestsObjC singlePrimaryResponder]
bool singlePrimaryResponder()
Definition: FlutterKeyboardManagerTest.mm:481
KeyboardTester::manager
FlutterKeyboardManager * manager
Definition: FlutterKeyboardManagerTest.mm:220
-[FlutterKeyboardManagerUnittestsObjC shouldNotHoldStrongReferenceToDelegate]
bool shouldNotHoldStrongReferenceToDelegate()
Definition: FlutterKeyboardManagerTest.mm:828
_messengerMock
NSObject< FlutterBinaryMessenger > * _messengerMock
Definition: FlutterKeyboardManagerTest.mm:249
-[KeyboardTester lastKeyboardChannelResult]
id lastKeyboardChannelResult()
Definition: FlutterKeyboardManagerTest.mm:303
_keyboardChannelResult
id _keyboardChannelResult
Definition: FlutterKeyboardManagerTest.mm:248
FlutterKeyboardManager.h
VERIFY_DOWN
#define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR)
Definition: FlutterKeyboardManagerTest.mm:173
_keyboardHandler
FlutterBinaryMessageHandler _keyboardHandler
Definition: FlutterKeyboardManagerTest.mm:250
-[KeyboardTester respondChannelCallsWith:]
void respondChannelCallsWith:(BOOL response)
Definition: FlutterKeyboardManagerTest.mm:334
FlutterKeyboardManagerUnittestsObjC
Definition: FlutterKeyboardManagerTest.mm:427
FlutterKeyboardLayout
Definition: FlutterKeyboardLayout.h:50
FlutterKeyboardManager
Definition: FlutterKeyboardManager.h:78
_textCallback
TextInputCallback _textCallback
Definition: FlutterKeyboardManagerTest.mm:240
FlutterBinaryMessenger-p
Definition: FlutterBinaryMessenger.h:49
-[FlutterKeyboardManagerUnittestsObjC textInputPlugin]
bool textInputPlugin()
Definition: FlutterKeyboardManagerTest.mm:563
FlutterStandardMethodCodec
Definition: FlutterCodecs.h:469
_typeStorage
NSMutableArray< NSNumber * > * _typeStorage
Definition: FlutterKeyboardManagerTest.mm:242
_eventContextMock
NSObject< FlutterKeyboardManagerEventContext > * _eventContextMock
Definition: FlutterKeyboardManagerTest.mm:252
KeyboardTester
Definition: FlutterKeyboardManagerTest.mm:181
FlutterBinaryReply
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
-[KeyboardTester sendKeyboardChannelMessage:]
void sendKeyboardChannelMessage:(NSData *_Nullable message)
Definition: FlutterKeyboardManagerTest.mm:358
+[FlutterMessageCodec-p sharedInstance]
instancetype sharedInstance()
-[FlutterKeyboardManagerUnittestsObjC doublePrimaryResponder]
bool doublePrimaryResponder()
Definition: FlutterKeyboardManagerTest.mm:503
FlutterJSONMessageCodec
Definition: FlutterCodecs.h:81
_currentLayout
const MockLayoutData * _currentLayout
Definition: FlutterKeyboardManagerTest.mm:246