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