Flutter iOS Embedder
FlutterEmbedderKeyResponderTest.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 #import <Foundation/Foundation.h>
6 #import <OCMock/OCMock.h>
7 #import <XCTest/XCTest.h>
8 #include <_types/_uint64_t.h>
9 
14 #include "flutter/shell/platform/embedder/embedder.h"
15 #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
16 
17 using namespace flutter::testing::keycodes;
18 using namespace flutter::testing;
19 
21 
22 #define XCTAssertStrEqual(value, expected) \
23  XCTAssertTrue(strcmp(value, expected) == 0, \
24  @"String \"%s\" not equal to the expected value of \"%s\"", value, expected)
25 
26 // A wrap to convert FlutterKeyEvent to a ObjC class.
27 @interface TestKeyEvent : NSObject
28 @property(nonatomic) FlutterKeyEvent* data;
29 @property(nonatomic) FlutterKeyEventCallback callback;
30 @property(nonatomic) void* _Nullable userData;
31 - (nonnull instancetype)initWithEvent:(const FlutterKeyEvent*)event
32  callback:(nullable FlutterKeyEventCallback)callback
33  userData:(void* _Nullable)userData;
34 - (BOOL)hasCallback;
35 - (void)respond:(BOOL)handled;
36 @end
37 
38 @implementation TestKeyEvent
39 - (instancetype)initWithEvent:(const FlutterKeyEvent*)event
40  callback:(nullable FlutterKeyEventCallback)callback
41  userData:(void* _Nullable)userData {
42  self = [super init];
43  _data = new FlutterKeyEvent(*event);
44  if (event->character != nullptr) {
45  size_t len = strlen(event->character);
46  char* character = new char[len + 1];
47  strlcpy(character, event->character, len + 1);
48  _data->character = character;
49  }
50  _callback = callback;
51  _userData = userData;
52  return self;
53 }
54 
55 - (BOOL)hasCallback {
56  return _callback != nil;
57 }
58 
59 - (void)respond:(BOOL)handled {
60  NSAssert(
61  _callback != nil,
62  @"Improper call to `respond` that does not have a callback."); // Caller's responsibility
63  _callback(handled, _userData);
64 }
65 
66 - (void)dealloc {
67  if (_data->character != nullptr) {
68  delete[] _data->character;
69  }
70  delete _data;
71 }
72 @end
73 
74 namespace {
75 API_AVAILABLE(ios(13.4))
76 constexpr UIKeyboardHIDUsage kKeyCodeUndefined = (UIKeyboardHIDUsage)0x03;
77 API_AVAILABLE(ios(13.4))
78 constexpr UIKeyboardHIDUsage kKeyCodeKeyA = (UIKeyboardHIDUsage)0x04;
79 API_AVAILABLE(ios(13.4))
80 constexpr UIKeyboardHIDUsage kKeyCodePeriod = (UIKeyboardHIDUsage)0x37;
81 API_AVAILABLE(ios(13.4))
82 constexpr UIKeyboardHIDUsage kKeyCodeKeyW = (UIKeyboardHIDUsage)0x1a;
83 API_AVAILABLE(ios(13.4))
84 constexpr UIKeyboardHIDUsage kKeyCodeShiftLeft = (UIKeyboardHIDUsage)0xe1;
85 API_AVAILABLE(ios(13.4))
86 constexpr UIKeyboardHIDUsage kKeyCodeShiftRight = (UIKeyboardHIDUsage)0xe5;
87 API_AVAILABLE(ios(13.4))
88 constexpr UIKeyboardHIDUsage kKeyCodeNumpad1 = (UIKeyboardHIDUsage)0x59;
89 API_AVAILABLE(ios(13.4))
90 constexpr UIKeyboardHIDUsage kKeyCodeCapsLock = (UIKeyboardHIDUsage)0x39;
91 API_AVAILABLE(ios(13.4))
92 constexpr UIKeyboardHIDUsage kKeyCodeF1 = (UIKeyboardHIDUsage)0x3a;
93 API_AVAILABLE(ios(13.4))
94 constexpr UIKeyboardHIDUsage kKeyCodeCommandLeft = (UIKeyboardHIDUsage)0xe3;
95 API_AVAILABLE(ios(13.4))
96 constexpr UIKeyboardHIDUsage kKeyCodeAltRight = (UIKeyboardHIDUsage)0xe6;
97 API_AVAILABLE(ios(13.4))
98 constexpr UIKeyboardHIDUsage kKeyCodeEject = (UIKeyboardHIDUsage)0xb8;
99 
100 constexpr uint64_t kPhysicalKeyUndefined = 0x00070003;
101 
102 constexpr uint64_t kLogicalKeyUndefined = 0x1300000003;
103 
104 constexpr uint64_t kModifierFlagNone = 0x0;
105 
106 typedef void (^ResponseCallback)(bool handled);
107 } // namespace
108 
109 @interface FlutterEmbedderKeyResponderTest : XCTestCase
110 @end
111 
112 @implementation FlutterEmbedderKeyResponderTest
113 
114 - (void)setUp {
115  // All of these tests were designed to run on iOS 13.4 or later.
116  if (@available(iOS 13.4, *)) {
117  } else {
118  XCTSkip(@"Required API not present for test.");
119  }
120 }
121 
122 - (void)tearDown {
123 }
124 
125 // Test the most basic key events.
126 //
127 // Press, hold, and release key A on an US keyboard.
128 - (void)testBasicKeyEvent API_AVAILABLE(ios(13.4)) {
129  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
130  __block BOOL last_handled = TRUE;
131  FlutterKeyEvent* event;
132 
134  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
135  void* _Nullable user_data) {
136  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event
137  callback:callback
138  userData:user_data]];
139  }];
140 
141  last_handled = FALSE;
142  [responder handlePress:keyDownEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f, "a", "a")
143  callback:^(BOOL handled) {
144  last_handled = handled;
145  }];
146 
147  XCTAssertEqual([events count], 1u);
148  event = [events lastObject].data;
149  XCTAssertNotEqual(event, nullptr);
150  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
151  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
152  XCTAssertEqual(event->timestamp, 123000000.0f);
153  XCTAssertEqual(event->physical, kPhysicalKeyA);
154  XCTAssertEqual(event->logical, kLogicalKeyA);
155  XCTAssertStrEqual(event->character, "a");
156  XCTAssertEqual(event->synthesized, false);
157 
158  XCTAssertEqual(last_handled, FALSE);
159  XCTAssert([[events lastObject] hasCallback]);
160  [[events lastObject] respond:TRUE];
161  XCTAssertEqual(last_handled, TRUE);
162 
163  [events removeAllObjects];
164 
165  last_handled = TRUE;
166  [responder handlePress:keyUpEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f)
167  callback:^(BOOL handled) {
168  last_handled = handled;
169  }];
170 
171  XCTAssertEqual([events count], 1u);
172  event = [events lastObject].data;
173  XCTAssertNotEqual(event, nullptr);
174  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
175  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
176  XCTAssertEqual(event->timestamp, 123000000.0f);
177  XCTAssertEqual(event->physical, kPhysicalKeyA);
178  XCTAssertEqual(event->logical, kLogicalKeyA);
179  XCTAssertEqual(event->character, nullptr);
180  XCTAssertEqual(event->synthesized, false);
181 
182  XCTAssertEqual(last_handled, TRUE);
183  XCTAssert([[events lastObject] hasCallback]);
184  [[events lastObject] respond:FALSE]; // Check if responding FALSE works
185  XCTAssertEqual(last_handled, FALSE);
186 
187  [events removeAllObjects];
188 }
189 
190 - (void)testIosKeyPlane API_AVAILABLE(ios(13.4)) {
191  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
192  __block BOOL last_handled = TRUE;
193  FlutterKeyEvent* event;
194 
196  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
197  void* _Nullable user_data) {
198  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event
199  callback:callback
200  userData:user_data]];
201  }];
202 
203  last_handled = FALSE;
204  // Verify that the eject key (keycode 0xb8, which is not present in the keymap)
205  // should be translated to the right logical and physical keys.
206  [responder handlePress:keyDownEvent(kKeyCodeEject, kModifierFlagNone, 123.0f)
207  callback:^(BOOL handled) {
208  last_handled = handled;
209  }];
210 
211  XCTAssertEqual([events count], 1u);
212  event = [events lastObject].data;
213  XCTAssertNotEqual(event, nullptr);
214  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
215  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
216  XCTAssertEqual(event->physical, kKeyCodeEject | kIosPlane);
217  XCTAssertEqual(event->logical, kKeyCodeEject | kIosPlane);
218  XCTAssertEqual(event->character, nullptr);
219  XCTAssertEqual(event->synthesized, false);
220 
221  XCTAssertEqual(last_handled, FALSE);
222  XCTAssert([[events lastObject] hasCallback]);
223  [[events lastObject] respond:TRUE];
224  XCTAssertEqual(last_handled, TRUE);
225 
226  [events removeAllObjects];
227 
228  last_handled = TRUE;
229  [responder handlePress:keyUpEvent(kKeyCodeEject, kModifierFlagNone, 123.0f)
230  callback:^(BOOL handled) {
231  last_handled = handled;
232  }];
233 
234  XCTAssertEqual([events count], 1u);
235  event = [events lastObject].data;
236  XCTAssertNotEqual(event, nullptr);
237  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
238  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
239  XCTAssertEqual(event->physical, kKeyCodeEject | kIosPlane);
240  XCTAssertEqual(event->logical, kKeyCodeEject | kIosPlane);
241  XCTAssertEqual(event->character, nullptr);
242  XCTAssertEqual(event->synthesized, false);
243 
244  XCTAssertEqual(last_handled, TRUE);
245  XCTAssert([[events lastObject] hasCallback]);
246  [[events lastObject] respond:FALSE]; // Check if responding FALSE works
247  XCTAssertEqual(last_handled, FALSE);
248 
249  [events removeAllObjects];
250 }
251 
252 - (void)testOutOfOrderModifiers API_AVAILABLE(ios(13.4)) {
253  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
254  FlutterKeyEvent* event;
255 
257  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
258  void* _Nullable user_data) {
259  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event
260  callback:callback
261  userData:user_data]];
262  }];
263 
264  // This tests that we synthesize the correct modifier keys when we release the
265  // modifier key that created the letter before we release the letter.
266  [responder handlePress:keyDownEvent(kKeyCodeAltRight, kModifierFlagAltAny, 123.0f)
267  callback:^(BOOL handled){
268  }];
269 
270  XCTAssertEqual([events count], 1u);
271  event = [events lastObject].data;
272  XCTAssertNotEqual(event, nullptr);
273  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
274  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
275  XCTAssertEqual(event->physical, kPhysicalAltRight);
276  XCTAssertEqual(event->logical, kLogicalAltRight);
277  XCTAssertEqual(event->character, nullptr);
278  XCTAssertEqual(event->synthesized, false);
279 
280  [events removeAllObjects];
281 
282  // Test non-ASCII characters being produced.
283  [responder handlePress:keyDownEvent(kKeyCodeKeyW, kModifierFlagAltAny, 123.0f, "∑", "w")
284  callback:^(BOOL handled){
285  }];
286 
287  XCTAssertEqual([events count], 1u);
288  event = [events lastObject].data;
289  XCTAssertNotEqual(event, nullptr);
290  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
291  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
292  XCTAssertEqual(event->physical, kPhysicalKeyW);
293  XCTAssertEqual(event->logical, kLogicalKeyW);
294  XCTAssertStrEqual(event->character, "∑");
295  XCTAssertEqual(event->synthesized, false);
296 
297  [events removeAllObjects];
298 
299  // Releasing the modifier key before the letter should send the key up to the
300  // framework.
301  [responder handlePress:keyUpEvent(kKeyCodeAltRight, kModifierFlagAltAny, 123.0f)
302  callback:^(BOOL handled){
303  }];
304 
305  XCTAssertEqual([events count], 1u);
306  event = [events lastObject].data;
307  XCTAssertNotEqual(event, nullptr);
308  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
309  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
310  XCTAssertEqual(event->physical, kPhysicalAltRight);
311  XCTAssertEqual(event->logical, kLogicalAltRight);
312  XCTAssertEqual(event->character, nullptr);
313  XCTAssertEqual(event->synthesized, false);
314 
315  [events removeAllObjects];
316 
317  // Yes, iOS sends a modifier flag for the Alt key being down on this event,
318  // even though the Alt (Option) key has already been released. This means that
319  // for the framework to be in the correct state, we must synthesize a key down
320  // event for the modifier key here, and another key up before the next key
321  // event.
322  [responder handlePress:keyUpEvent(kKeyCodeKeyW, kModifierFlagAltAny, 123.0f)
323  callback:^(BOOL handled){
324  }];
325 
326  XCTAssertEqual([events count], 1u);
327  event = [events lastObject].data;
328  XCTAssertNotEqual(event, nullptr);
329  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
330  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
331  XCTAssertEqual(event->physical, kPhysicalKeyW);
332  XCTAssertEqual(event->logical, kLogicalKeyW);
333  XCTAssertEqual(event->character, nullptr);
334  XCTAssertEqual(event->synthesized, false);
335 
336  [events removeAllObjects];
337 
338  // Here we should simulate a key up for the Alt key, since it is no longer
339  // shown as down in the modifier flags.
340  [responder handlePress:keyDownEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f, "Ã¥", "a")
341  callback:^(BOOL handled){
342  }];
343 
344  XCTAssertEqual([events count], 1u);
345  event = [events lastObject].data;
346  XCTAssertNotEqual(event, nullptr);
347  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
348  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
349  XCTAssertEqual(event->physical, kPhysicalKeyA);
350  XCTAssertEqual(event->logical, kLogicalKeyA);
351  XCTAssertStrEqual(event->character, "Ã¥");
352  XCTAssertEqual(event->synthesized, false);
353 }
354 
355 - (void)testIgnoreDuplicateDownEvent API_AVAILABLE(ios(13.4)) {
356  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
357  __block BOOL last_handled = TRUE;
358  FlutterKeyEvent* event;
359 
361  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
362  void* _Nullable user_data) {
363  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event
364  callback:callback
365  userData:user_data]];
366  }];
367 
368  last_handled = FALSE;
369  [responder handlePress:keyDownEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f, "a", "a")
370  callback:^(BOOL handled) {
371  last_handled = handled;
372  }];
373 
374  XCTAssertEqual([events count], 1u);
375  event = [events lastObject].data;
376  XCTAssertNotEqual(event, nullptr);
377  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
378  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
379  XCTAssertEqual(event->physical, kPhysicalKeyA);
380  XCTAssertEqual(event->logical, kLogicalKeyA);
381  XCTAssertStrEqual(event->character, "a");
382  XCTAssertEqual(event->synthesized, false);
383  XCTAssertEqual(last_handled, FALSE);
384  [[events lastObject] respond:TRUE];
385  XCTAssertEqual(last_handled, TRUE);
386 
387  [events removeAllObjects];
388 
389  last_handled = FALSE;
390  [responder handlePress:keyDownEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f, "a", "a")
391  callback:^(BOOL handled) {
392  last_handled = handled;
393  }];
394 
395  XCTAssertEqual([events count], 1u);
396  event = [events lastObject].data;
397  XCTAssertNotEqual(event, nullptr);
398  XCTAssertEqual(event->physical, 0ull);
399  XCTAssertEqual(event->logical, 0ull);
400  XCTAssertEqual(event->synthesized, false);
401  XCTAssertFalse([[events lastObject] hasCallback]);
402  XCTAssertEqual(last_handled, TRUE);
403 
404  [events removeAllObjects];
405 
406  last_handled = FALSE;
407  [responder handlePress:keyUpEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f)
408  callback:^(BOOL handled) {
409  last_handled = handled;
410  }];
411 
412  XCTAssertEqual([events count], 1u);
413  event = [events lastObject].data;
414  XCTAssertNotEqual(event, nullptr);
415  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
416  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
417  XCTAssertEqual(event->physical, kPhysicalKeyA);
418  XCTAssertEqual(event->logical, kLogicalKeyA);
419  XCTAssertEqual(event->character, nullptr);
420  XCTAssertEqual(event->synthesized, false);
421  XCTAssertEqual(last_handled, FALSE);
422  [[events lastObject] respond:TRUE];
423  XCTAssertEqual(last_handled, TRUE);
424 
425  [events removeAllObjects];
426 }
427 
428 - (void)testIgnoreAbruptUpEvent API_AVAILABLE(ios(13.4)) {
429  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
430  __block BOOL last_handled = TRUE;
431  FlutterKeyEvent* event;
432 
434  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
435  void* _Nullable user_data) {
436  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event
437  callback:callback
438  userData:user_data]];
439  }];
440 
441  last_handled = FALSE;
442  [responder handlePress:keyUpEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f)
443  callback:^(BOOL handled) {
444  last_handled = handled;
445  }];
446 
447  XCTAssertEqual([events count], 1u);
448  event = [events lastObject].data;
449  XCTAssertNotEqual(event, nullptr);
450  XCTAssertEqual(event->physical, 0ull);
451  XCTAssertEqual(event->logical, 0ull);
452  XCTAssertEqual(event->synthesized, false);
453  XCTAssertFalse([[events lastObject] hasCallback]);
454  XCTAssertEqual(last_handled, TRUE);
455 
456  [events removeAllObjects];
457 }
458 
459 // Press R-Shift, A, then release R-Shift then A, on a US keyboard.
460 //
461 // This is special because the characters for the A key will change in this
462 // process.
463 - (void)testToggleModifiersDuringKeyTap API_AVAILABLE(ios(13.4)) {
464  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
465  FlutterKeyEvent* event;
466 
468  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
469  void* _Nullable user_data) {
470  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event
471  callback:callback
472  userData:user_data]];
473  }];
474 
475  [responder handlePress:keyDownEvent(kKeyCodeShiftRight, kModifierFlagShiftAny, 123.0f)
476  callback:^(BOOL handled){
477  }];
478 
479  XCTAssertEqual([events count], 1u);
480 
481  event = [events lastObject].data;
482  XCTAssertNotEqual(event, nullptr);
483  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
484  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
485  XCTAssertEqual(event->timestamp, 123000000.0f);
486  XCTAssertEqual(event->physical, kPhysicalShiftRight);
487  XCTAssertEqual(event->logical, kLogicalShiftRight);
488  XCTAssertEqual(event->character, nullptr);
489  XCTAssertEqual(event->synthesized, false);
490  [[events lastObject] respond:TRUE];
491 
492  [events removeAllObjects];
493 
494  [responder handlePress:keyDownEvent(kKeyCodeKeyA, kModifierFlagShiftAny, 123.0f, "A", "A")
495  callback:^(BOOL handled){
496  }];
497 
498  XCTAssertEqual([events count], 1u);
499  event = [events lastObject].data;
500  XCTAssertNotEqual(event, nullptr);
501  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
502  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
503  XCTAssertEqual(event->physical, kPhysicalKeyA);
504  XCTAssertEqual(event->logical, kLogicalKeyA);
505  XCTAssertStrEqual(event->character, "A");
506  XCTAssertEqual(event->synthesized, false);
507  [[events lastObject] respond:TRUE];
508 
509  [events removeAllObjects];
510 
511  [responder handlePress:keyUpEvent(kKeyCodeShiftRight, kModifierFlagNone, 123.0f)
512  callback:^(BOOL handled){
513  }];
514 
515  XCTAssertEqual([events count], 1u);
516  event = [events lastObject].data;
517  XCTAssertNotEqual(event, nullptr);
518  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
519  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
520  XCTAssertEqual(event->physical, kPhysicalShiftRight);
521  XCTAssertEqual(event->logical, kLogicalShiftRight);
522  XCTAssertEqual(event->character, nullptr);
523  XCTAssertEqual(event->synthesized, false);
524  [[events lastObject] respond:TRUE];
525 
526  [events removeAllObjects];
527 
528  [responder handlePress:keyUpEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f)
529  callback:^(BOOL handled){
530  }];
531 
532  XCTAssertEqual([events count], 1u);
533  event = [events lastObject].data;
534  XCTAssertNotEqual(event, nullptr);
535  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
536  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
537  XCTAssertEqual(event->physical, kPhysicalKeyA);
538  XCTAssertEqual(event->logical, kLogicalKeyA);
539  XCTAssertEqual(event->character, nullptr);
540  XCTAssertEqual(event->synthesized, false);
541  [[events lastObject] respond:TRUE];
542 
543  [events removeAllObjects];
544 }
545 
546 // Special modifier flags.
547 //
548 // Some keys in modifierFlags are not to indicate modifier state, but to mark
549 // the key area that the key belongs to, such as numpad keys or function keys.
550 // Ensure these flags do not obstruct other keys.
551 - (void)testSpecialModiferFlags API_AVAILABLE(ios(13.4)) {
552  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
553  FlutterKeyEvent* event;
554  __block BOOL last_handled = TRUE;
555  id keyEventCallback = ^(BOOL handled) {
556  last_handled = handled;
557  };
558 
560  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
561  void* _Nullable user_data) {
562  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event
563  callback:callback
564  userData:user_data]];
565  }];
566 
567  // Keydown: Numpad1, Fn (undefined), F1, KeyA, ShiftLeft
568  // Then KeyUp: Numpad1, Fn (undefined), F1, KeyA, ShiftLeft
569 
570  // Numpad 1
571  // OS provides: char: "1", code: 0x59, modifiers: 0x200000
572  [responder handlePress:keyDownEvent(kKeyCodeNumpad1, kModifierFlagNumPadKey, 123.0, "1", "1")
573  callback:keyEventCallback];
574 
575  XCTAssertEqual([events count], 1u);
576  event = [events lastObject].data;
577  XCTAssertNotEqual(event, nullptr);
578  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
579  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
580  XCTAssertEqual(event->physical, kPhysicalNumpad1);
581  XCTAssertEqual(event->logical, kLogicalNumpad1);
582  XCTAssertStrEqual(event->character, "1");
583  XCTAssertEqual(event->synthesized, false);
584  [[events lastObject] respond:TRUE];
585 
586  [events removeAllObjects];
587 
588  // Fn Key (sends HID undefined)
589  // OS provides: char: nil, keycode: 0x3, modifiers: 0x0
590  [responder handlePress:keyDownEvent(kKeyCodeUndefined, kModifierFlagNone, 123.0)
591  callback:keyEventCallback];
592 
593  XCTAssertEqual([events count], 1u);
594  event = [events lastObject].data;
595  XCTAssertNotEqual(event, nullptr);
596  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
597  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
598  XCTAssertEqual(event->physical, kPhysicalKeyUndefined);
599  XCTAssertEqual(event->logical, kLogicalKeyUndefined);
600  XCTAssertEqual(event->character, nullptr);
601  XCTAssertEqual(event->synthesized, false);
602  [[events lastObject] respond:TRUE];
603 
604  [events removeAllObjects];
605 
606  // F1 Down
607  // OS provides: char: UIKeyInputF1, code: 0x3a, modifiers: 0x0
608  [responder handlePress:keyDownEvent(kKeyCodeF1, kModifierFlagNone, 123.0f, "\\^P", "\\^P")
609  callback:keyEventCallback];
610 
611  XCTAssertEqual([events count], 1u);
612  event = [events lastObject].data;
613  XCTAssertNotEqual(event, nullptr);
614  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
615  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
616  XCTAssertEqual(event->physical, kPhysicalF1);
617  XCTAssertEqual(event->logical, kLogicalF1);
618  XCTAssertEqual(event->character, nullptr);
619  XCTAssertEqual(event->synthesized, false);
620  [[events lastObject] respond:TRUE];
621 
622  [events removeAllObjects];
623 
624  // KeyA Down
625  // OS provides: char: "q", code: 0x4, modifiers: 0x0
626  [responder handlePress:keyDownEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f, "a", "a")
627  callback:keyEventCallback];
628 
629  XCTAssertEqual([events count], 1u);
630  event = [events lastObject].data;
631  XCTAssertNotEqual(event, nullptr);
632  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
633  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
634  XCTAssertEqual(event->physical, kPhysicalKeyA);
635  XCTAssertEqual(event->logical, kLogicalKeyA);
636  XCTAssertStrEqual(event->character, "a");
637  XCTAssertEqual(event->synthesized, false);
638  [[events lastObject] respond:TRUE];
639 
640  [events removeAllObjects];
641 
642  // ShiftLeft Down
643  // OS Provides: char: nil, code: 0xe1, modifiers: 0x20000
644  [responder handlePress:keyDownEvent(kKeyCodeShiftLeft, kModifierFlagShiftAny, 123.0f)
645  callback:keyEventCallback];
646 
647  XCTAssertEqual([events count], 1u);
648  event = [events lastObject].data;
649  XCTAssertNotEqual(event, nullptr);
650  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
651  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
652  XCTAssertEqual(event->physical, kPhysicalShiftLeft);
653  XCTAssertEqual(event->logical, kLogicalShiftLeft);
654  XCTAssertEqual(event->character, nullptr);
655  XCTAssertEqual(event->synthesized, false);
656 
657  [events removeAllObjects];
658 
659  // Numpad 1 Up
660  // OS provides: char: "1", code: 0x59, modifiers: 0x200000
661  [responder handlePress:keyUpEvent(kKeyCodeNumpad1, kModifierFlagNumPadKey, 123.0f)
662  callback:keyEventCallback];
663 
664  XCTAssertEqual([events count], 2u);
665 
666  // Because the OS no longer provides the 0x20000 (kModifierFlagShiftAny), we
667  // have to simulate a keyup.
668  event = [events firstObject].data;
669  XCTAssertNotEqual(event, nullptr);
670  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
671  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
672  XCTAssertEqual(event->physical, kPhysicalShiftLeft);
673  XCTAssertEqual(event->logical, kLogicalShiftLeft);
674  XCTAssertEqual(event->character, nullptr);
675  XCTAssertEqual(event->synthesized, true);
676 
677  event = [events lastObject].data;
678  XCTAssertNotEqual(event, nullptr);
679  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
680  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
681  XCTAssertEqual(event->physical, kPhysicalNumpad1);
682  XCTAssertEqual(event->logical, kLogicalNumpad1);
683  XCTAssertEqual(event->character, nullptr);
684  XCTAssertEqual(event->synthesized, false);
685  [[events lastObject] respond:TRUE];
686 
687  [events removeAllObjects];
688 
689  // F1 Up
690  // OS provides: char: UIKeyInputF1, code: 0x3a, modifiers: 0x0
691  [responder handlePress:keyUpEvent(kKeyCodeF1, kModifierFlagNone, 123.0f)
692  callback:keyEventCallback];
693 
694  XCTAssertEqual([events count], 1u);
695  event = [events lastObject].data;
696  XCTAssertNotEqual(event, nullptr);
697  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
698  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
699  XCTAssertEqual(event->physical, kPhysicalF1);
700  XCTAssertEqual(event->logical, kLogicalF1);
701  XCTAssertEqual(event->character, nullptr);
702  XCTAssertEqual(event->synthesized, false);
703  [[events lastObject] respond:TRUE];
704 
705  [events removeAllObjects];
706 
707  // Fn Key (sends HID undefined)
708  // OS provides: char: nil, code: 0x3, modifiers: 0x0
709  [responder handlePress:keyUpEvent(kKeyCodeUndefined, kModifierFlagNone, 123.0)
710  callback:keyEventCallback];
711 
712  XCTAssertEqual([events count], 1u);
713  event = [events lastObject].data;
714  XCTAssertNotEqual(event, nullptr);
715  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
716  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
717  XCTAssertEqual(event->physical, kPhysicalKeyUndefined);
718  XCTAssertEqual(event->logical, kLogicalKeyUndefined);
719  XCTAssertEqual(event->synthesized, false);
720  [[events lastObject] respond:TRUE];
721 
722  [events removeAllObjects];
723 
724  // KeyA Up
725  // OS provides: char: "a", code: 0x4, modifiers: 0x0
726  [responder handlePress:keyUpEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f)
727  callback:keyEventCallback];
728 
729  XCTAssertEqual([events count], 1u);
730  event = [events lastObject].data;
731  XCTAssertNotEqual(event, nullptr);
732  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
733  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
734  XCTAssertEqual(event->physical, kPhysicalKeyA);
735  XCTAssertEqual(event->logical, kLogicalKeyA);
736  XCTAssertEqual(event->character, nullptr);
737  XCTAssertEqual(event->synthesized, false);
738  [[events lastObject] respond:TRUE];
739 
740  [events removeAllObjects];
741 
742  // ShiftLeft Up
743  // OS provides: char: nil, code: 0xe1, modifiers: 0x20000
744  [responder handlePress:keyUpEvent(kKeyCodeShiftLeft, kModifierFlagShiftAny, 123.0f)
745  callback:keyEventCallback];
746 
747  XCTAssertEqual([events count], 1u);
748  event = [events lastObject].data;
749  XCTAssertNotEqual(event, nullptr);
750  XCTAssertEqual(event->physical, 0ull);
751  XCTAssertEqual(event->logical, 0ull);
752  XCTAssertEqual(event->synthesized, false);
753  XCTAssertFalse([[events lastObject] hasCallback]);
754  XCTAssertEqual(last_handled, TRUE);
755 
756  [events removeAllObjects];
757 }
758 
759 - (void)testIdentifyLeftAndRightModifiers API_AVAILABLE(ios(13.4)) {
760  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
761  FlutterKeyEvent* event;
762 
764  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
765  void* _Nullable user_data) {
766  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event
767  callback:callback
768  userData:user_data]];
769  }];
770 
771  [responder handlePress:keyDownEvent(kKeyCodeShiftLeft, kModifierFlagShiftAny, 123.0f)
772  callback:^(BOOL handled){
773  }];
774 
775  XCTAssertEqual([events count], 1u);
776  event = [events lastObject].data;
777  XCTAssertNotEqual(event, nullptr);
778  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
779  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
780  XCTAssertEqual(event->physical, kPhysicalShiftLeft);
781  XCTAssertEqual(event->logical, kLogicalShiftLeft);
782  XCTAssertEqual(event->character, nullptr);
783  XCTAssertEqual(event->synthesized, false);
784  [[events lastObject] respond:TRUE];
785 
786  [events removeAllObjects];
787 
788  [responder handlePress:keyDownEvent(kKeyCodeShiftRight, kModifierFlagShiftAny, 123.0f)
789  callback:^(BOOL handled){
790  }];
791 
792  XCTAssertEqual([events count], 1u);
793  event = [events lastObject].data;
794  XCTAssertNotEqual(event, nullptr);
795  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
796  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
797  XCTAssertEqual(event->physical, kPhysicalShiftRight);
798  XCTAssertEqual(event->logical, kLogicalShiftRight);
799  XCTAssertEqual(event->character, nullptr);
800  XCTAssertEqual(event->synthesized, false);
801  [[events lastObject] respond:TRUE];
802 
803  [events removeAllObjects];
804 
805  [responder handlePress:keyUpEvent(kKeyCodeShiftLeft, kModifierFlagShiftAny, 123.0f)
806  callback:^(BOOL handled){
807  }];
808 
809  XCTAssertEqual([events count], 1u);
810  event = [events lastObject].data;
811  XCTAssertNotEqual(event, nullptr);
812  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
813  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
814  XCTAssertEqual(event->physical, kPhysicalShiftLeft);
815  XCTAssertEqual(event->logical, kLogicalShiftLeft);
816  XCTAssertEqual(event->character, nullptr);
817  XCTAssertEqual(event->synthesized, false);
818  [[events lastObject] respond:TRUE];
819 
820  [events removeAllObjects];
821 
822  [responder handlePress:keyUpEvent(kKeyCodeShiftRight, kModifierFlagShiftAny, 123.0f)
823  callback:^(BOOL handled){
824  }];
825 
826  XCTAssertEqual([events count], 1u);
827  event = [events lastObject].data;
828  XCTAssertNotEqual(event, nullptr);
829  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
830  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
831  XCTAssertEqual(event->physical, kPhysicalShiftRight);
832  XCTAssertEqual(event->logical, kLogicalShiftRight);
833  XCTAssertEqual(event->character, nullptr);
834  XCTAssertEqual(event->synthesized, false);
835  [[events lastObject] respond:TRUE];
836 
837  [events removeAllObjects];
838 }
839 
840 // Press the CapsLock key when CapsLock state is desynchronized
841 - (void)testSynchronizeCapsLockStateOnCapsLock API_AVAILABLE(ios(13.4)) {
842  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
843  __block BOOL last_handled = TRUE;
844  id keyEventCallback = ^(BOOL handled) {
845  last_handled = handled;
846  };
847  FlutterKeyEvent* event;
848 
850  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
851  void* _Nullable user_data) {
852  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event
853  callback:callback
854  userData:user_data]];
855  }];
856 
857  last_handled = FALSE;
858  [responder handlePress:keyDownEvent(kKeyCodeKeyA, kModifierFlagCapsLock, 123.0f, "A", "A")
859  callback:keyEventCallback];
860 
861  XCTAssertEqual([events count], 3u);
862 
863  event = events[0].data;
864  XCTAssertNotEqual(event, nullptr);
865  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
866  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
867  XCTAssertEqual(event->physical, kPhysicalCapsLock);
868  XCTAssertEqual(event->logical, kLogicalCapsLock);
869  XCTAssertEqual(event->character, nullptr);
870  XCTAssertEqual(event->synthesized, true);
871  XCTAssertFalse([events[0] hasCallback]);
872 
873  event = events[1].data;
874  XCTAssertNotEqual(event, nullptr);
875  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
876  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
877  XCTAssertEqual(event->physical, kPhysicalCapsLock);
878  XCTAssertEqual(event->logical, kLogicalCapsLock);
879  XCTAssertEqual(event->character, nullptr);
880  XCTAssertEqual(event->synthesized, true);
881  XCTAssertFalse([events[1] hasCallback]);
882 
883  event = events[2].data;
884  XCTAssertNotEqual(event, nullptr);
885  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
886  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
887  XCTAssertEqual(event->physical, kPhysicalKeyA);
888  XCTAssertEqual(event->logical, kLogicalKeyA);
889  XCTAssertStrEqual(event->character, "A");
890  XCTAssertEqual(event->synthesized, false);
891  XCTAssert([events[2] hasCallback]);
892 
893  XCTAssertEqual(last_handled, FALSE);
894  [[events lastObject] respond:TRUE];
895  XCTAssertEqual(last_handled, TRUE);
896 
897  [events removeAllObjects];
898 
899  // Release the "A" key.
900  [responder handlePress:keyUpEvent(kKeyCodeKeyA, kModifierFlagCapsLock, 123.0f)
901  callback:keyEventCallback];
902  XCTAssertEqual([events count], 1u);
903  event = [events lastObject].data;
904  XCTAssertNotEqual(event, nullptr);
905  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
906  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
907  XCTAssertEqual(event->physical, kPhysicalKeyA);
908  XCTAssertEqual(event->logical, kLogicalKeyA);
909  XCTAssertEqual(event->synthesized, false);
910 
911  [events removeAllObjects];
912 
913  // In: CapsLock down
914  // Out: CapsLock down
915  last_handled = FALSE;
916  [responder handlePress:keyDownEvent(kKeyCodeCapsLock, kModifierFlagCapsLock, 123.0f)
917  callback:keyEventCallback];
918 
919  XCTAssertEqual([events count], 1u);
920  event = [events firstObject].data;
921  XCTAssertNotEqual(event, nullptr);
922  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
923  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
924  XCTAssertEqual(event->physical, kPhysicalCapsLock);
925  XCTAssertEqual(event->logical, kLogicalCapsLock);
926  XCTAssertEqual(event->character, nullptr);
927  XCTAssertEqual(event->synthesized, false);
928  XCTAssert([[events firstObject] hasCallback]);
929 
930  [events removeAllObjects];
931 
932  // In: CapsLock up
933  // Out: CapsLock up
934  // This turns off the caps lock, triggering a synthesized up/down to tell the
935  // framework that.
936  last_handled = FALSE;
937  [responder handlePress:keyUpEvent(kKeyCodeCapsLock, kModifierFlagCapsLock, 123.0f)
938  callback:keyEventCallback];
939 
940  XCTAssertEqual([events count], 1u);
941  event = [events firstObject].data;
942  XCTAssertNotEqual(event, nullptr);
943  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
944  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
945  XCTAssertEqual(event->physical, kPhysicalCapsLock);
946  XCTAssertEqual(event->logical, kLogicalCapsLock);
947  XCTAssertEqual(event->character, nullptr);
948  XCTAssertEqual(event->synthesized, false);
949  XCTAssert([[events firstObject] hasCallback]);
950 
951  [events removeAllObjects];
952 
953  last_handled = FALSE;
954  [responder handlePress:keyDownEvent(kKeyCodeKeyA, kModifierFlagNone, 123.0f, "a", "a")
955  callback:keyEventCallback];
956 
957  // Just to make sure that we aren't simulating events now, since the state is
958  // consistent, and should be off.
959  XCTAssertEqual([events count], 1u);
960  event = [events lastObject].data;
961  XCTAssertNotEqual(event, nullptr);
962  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
963  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
964  XCTAssertEqual(event->physical, kPhysicalKeyA);
965  XCTAssertEqual(event->logical, kLogicalKeyA);
966  XCTAssertStrEqual(event->character, "a");
967  XCTAssertEqual(event->synthesized, false);
968  XCTAssert([[events firstObject] hasCallback]);
969 }
970 
971 // Press the CapsLock key when CapsLock state is desynchronized
972 - (void)testSynchronizeCapsLockStateOnNormalKey API_AVAILABLE(ios(13.4)) {
973  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
974  __block BOOL last_handled = TRUE;
975  id keyEventCallback = ^(BOOL handled) {
976  last_handled = handled;
977  };
978  FlutterKeyEvent* event;
979 
981  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
982  void* _Nullable user_data) {
983  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event
984  callback:callback
985  userData:user_data]];
986  }];
987 
988  last_handled = FALSE;
989  [responder handlePress:keyDownEvent(kKeyCodeKeyA, kModifierFlagCapsLock, 123.0f, "A", "a")
990  callback:keyEventCallback];
991 
992  XCTAssertEqual([events count], 3u);
993 
994  event = events[0].data;
995  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
996  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
997  XCTAssertEqual(event->physical, kPhysicalCapsLock);
998  XCTAssertEqual(event->logical, kLogicalCapsLock);
999  XCTAssertEqual(event->character, nullptr);
1000  XCTAssertEqual(event->synthesized, true);
1001  XCTAssertFalse([events[0] hasCallback]);
1002 
1003  event = events[1].data;
1004  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
1005  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
1006  XCTAssertEqual(event->physical, kPhysicalCapsLock);
1007  XCTAssertEqual(event->logical, kLogicalCapsLock);
1008  XCTAssertEqual(event->character, nullptr);
1009  XCTAssertEqual(event->synthesized, true);
1010  XCTAssertFalse([events[1] hasCallback]);
1011 
1012  event = events[2].data;
1013  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
1014  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
1015  XCTAssertEqual(event->physical, kPhysicalKeyA);
1016  XCTAssertEqual(event->logical, kLogicalKeyA);
1017  XCTAssertStrEqual(event->character, "A");
1018  XCTAssertEqual(event->synthesized, false);
1019  XCTAssert([events[2] hasCallback]);
1020 
1021  XCTAssertEqual(last_handled, FALSE);
1022  [[events lastObject] respond:TRUE];
1023  XCTAssertEqual(last_handled, TRUE);
1024 
1025  [events removeAllObjects];
1026 }
1027 
1028 // Press Cmd-. should correctly result in an Escape event.
1029 - (void)testCommandPeriodKey API_AVAILABLE(ios(13.4)) {
1030  __block NSMutableArray<TestKeyEvent*>* events = [[NSMutableArray<TestKeyEvent*> alloc] init];
1031  id keyEventCallback = ^(BOOL handled) {
1032  };
1033  FlutterKeyEvent* event;
1034 
1036  initWithSendEvent:^(const FlutterKeyEvent& event, _Nullable FlutterKeyEventCallback callback,
1037  void* _Nullable user_data) {
1038  [events addObject:[[TestKeyEvent alloc] initWithEvent:&event callback:nil userData:nil]];
1039  callback(true, user_data);
1040  }];
1041 
1042  // MetaLeft down.
1043  [responder handlePress:keyDownEvent(kKeyCodeCommandLeft, kModifierFlagMetaAny, 123.0f, "", "")
1044  callback:keyEventCallback];
1045  XCTAssertEqual([events count], 1u);
1046  event = events[0].data;
1047  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
1048  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
1049  XCTAssertEqual(event->physical, kPhysicalMetaLeft);
1050  XCTAssertEqual(event->logical, kLogicalMetaLeft);
1051  XCTAssertEqual(event->character, nullptr);
1052  XCTAssertEqual(event->synthesized, false);
1053  [events removeAllObjects];
1054 
1055  // Period down, which is logically Escape.
1056  [responder handlePress:keyDownEvent(kKeyCodePeriod, kModifierFlagMetaAny, 123.0f,
1057  "UIKeyInputEscape", "UIKeyInputEscape")
1058  callback:keyEventCallback];
1059  XCTAssertEqual([events count], 1u);
1060  event = events[0].data;
1061  XCTAssertEqual(event->type, kFlutterKeyEventTypeDown);
1062  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
1063  XCTAssertEqual(event->physical, kPhysicalPeriod);
1064  XCTAssertEqual(event->logical, kLogicalEscape);
1065  XCTAssertEqual(event->character, nullptr);
1066  XCTAssertEqual(event->synthesized, false);
1067  [events removeAllObjects];
1068 
1069  // Period up, which unconventionally has characters.
1070  [responder handlePress:keyUpEvent(kKeyCodePeriod, kModifierFlagMetaAny, 123.0f,
1071  "UIKeyInputEscape", "UIKeyInputEscape")
1072  callback:keyEventCallback];
1073  XCTAssertEqual([events count], 1u);
1074  event = events[0].data;
1075  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
1076  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
1077  XCTAssertEqual(event->physical, kPhysicalPeriod);
1078  XCTAssertEqual(event->logical, kLogicalEscape);
1079  XCTAssertEqual(event->character, nullptr);
1080  XCTAssertEqual(event->synthesized, false);
1081  [events removeAllObjects];
1082 
1083  // MetaLeft up.
1084  [responder handlePress:keyUpEvent(kKeyCodeCommandLeft, kModifierFlagMetaAny, 123.0f, "", "")
1085  callback:keyEventCallback];
1086  XCTAssertEqual([events count], 1u);
1087  event = events[0].data;
1088  XCTAssertEqual(event->type, kFlutterKeyEventTypeUp);
1089  XCTAssertEqual(event->device_type, kFlutterKeyEventDeviceTypeKeyboard);
1090  XCTAssertEqual(event->physical, kPhysicalMetaLeft);
1091  XCTAssertEqual(event->logical, kLogicalMetaLeft);
1092  XCTAssertEqual(event->character, nullptr);
1093  XCTAssertEqual(event->synthesized, false);
1094  [events removeAllObjects];
1095 }
1096 
1097 @end
-[TestKeyEvent hasCallback]
BOOL hasCallback()
Definition: FlutterEmbedderKeyResponderTest.mm:55
TestKeyEvent
Definition: FlutterEmbedderKeyResponderTest.mm:27
API_AVAILABLE
UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0))
flutter::testing
Definition: FlutterFakeKeyEvents.h:51
-[FlutterKeyPrimaryResponder-p handlePress:callback:]
void handlePress:callback:(nonnull FlutterUIPressProxy *press,[callback] ios(13.4) API_AVAILABLE)
FlutterMacros.h
FlutterEmbedderKeyResponder.h
TestKeyEvent::callback
FlutterKeyEventCallback callback
Definition: FlutterEmbedderKeyResponderTest.mm:29
FLUTTER_ASSERT_ARC
FLUTTER_ASSERT_ARC
Definition: FlutterEmbedderKeyResponderTest.mm:20
XCTAssertStrEqual
#define XCTAssertStrEqual(value, expected)
Definition: FlutterEmbedderKeyResponderTest.mm:22
FlutterFakeKeyEvents.h
TestKeyEvent::userData
void *_Nullable userData
Definition: FlutterEmbedderKeyResponderTest.mm:30
KeyCodeMap_Internal.h
FlutterEmbedderKeyResponderTest
Definition: FlutterEmbedderKeyResponderTest.mm:109
FlutterEmbedderKeyResponder
Definition: FlutterEmbedderKeyResponder.h:27
kIosPlane
const uint64_t kIosPlane
Definition: KeyCodeMap.g.mm:32