|
8 | 8 | #import <XCTest/XCTest.h> |
9 | 9 | #include <_types/_uint32_t.h> |
10 | 10 |
|
| 11 | +#include "flutter/fml/platform/darwin/message_loop_darwin.h" |
11 | 12 | #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h" |
12 | 13 | #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterFakeKeyEvents.h" |
13 | 14 | #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterKeyboardManager.h" |
@@ -59,7 +60,8 @@ - (bool)emptyNextResponder; |
59 | 60 |
|
60 | 61 | namespace { |
61 | 62 |
|
62 | | -typedef void (^KeyCallbackSetter)(FlutterAsyncKeyCallback callback); |
| 63 | +typedef void (^KeyCallbackSetter)(FlutterUIPressProxy* press, FlutterAsyncKeyCallback callback) |
| 64 | + API_AVAILABLE(ios(13.4)); |
63 | 65 | typedef BOOL (^BoolGetter)(); |
64 | 66 |
|
65 | 67 | } // namespace |
@@ -100,11 +102,14 @@ - (id)checkKeyDownEvent:(UIKeyboardHIDUsage)keyCode API_AVAILABLE(ios(13.4)) { |
100 | 102 | OCMStrictProtocolMock(@protocol(FlutterKeyPrimaryResponder)); |
101 | 103 | OCMStub([mock handlePress:[OCMArg any] callback:[OCMArg any]]) |
102 | 104 | .andDo((^(NSInvocation* invocation) { |
| 105 | + FlutterUIPressProxy* press; |
103 | 106 | FlutterAsyncKeyCallback callback; |
| 107 | + [invocation getArgument:&press atIndex:2]; |
104 | 108 | [invocation getArgument:&callback atIndex:3]; |
105 | | - CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, ^() { |
106 | | - callbackSetter(callback); |
107 | | - }); |
| 109 | + CFRunLoopPerformBlock(CFRunLoopGetCurrent(), |
| 110 | + fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode, ^() { |
| 111 | + callbackSetter(press, callback); |
| 112 | + }); |
108 | 113 | })); |
109 | 114 | return mock; |
110 | 115 | } |
@@ -149,7 +154,8 @@ - (void)testSinglePrimaryResponder API_AVAILABLE(ios(13.4)) { |
149 | 154 | FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] init]; |
150 | 155 | __block BOOL primaryResponse = FALSE; |
151 | 156 | __block int callbackCount = 0; |
152 | | - [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterAsyncKeyCallback callback) { |
| 157 | + [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterUIPressProxy* press, |
| 158 | + FlutterAsyncKeyCallback callback) { |
153 | 159 | callbackCount++; |
154 | 160 | callback(primaryResponse); |
155 | 161 | }]]; |
@@ -181,14 +187,16 @@ - (void)testDoublePrimaryResponder API_AVAILABLE(ios(13.4)) { |
181 | 187 |
|
182 | 188 | __block BOOL callback1Response = FALSE; |
183 | 189 | __block int callback1Count = 0; |
184 | | - [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterAsyncKeyCallback callback) { |
| 190 | + [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterUIPressProxy* press, |
| 191 | + FlutterAsyncKeyCallback callback) { |
185 | 192 | callback1Count++; |
186 | 193 | callback(callback1Response); |
187 | 194 | }]]; |
188 | 195 |
|
189 | 196 | __block BOOL callback2Response = FALSE; |
190 | 197 | __block int callback2Count = 0; |
191 | | - [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterAsyncKeyCallback callback) { |
| 198 | + [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterUIPressProxy* press, |
| 199 | + FlutterAsyncKeyCallback callback) { |
192 | 200 | callback2Count++; |
193 | 201 | callback(callback2Response); |
194 | 202 | }]]; |
@@ -242,7 +250,8 @@ - (void)testSingleSecondaryResponder API_AVAILABLE(ios(13.4)) { |
242 | 250 |
|
243 | 251 | __block BOOL primaryResponse = FALSE; |
244 | 252 | __block int callbackCount = 0; |
245 | | - [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterAsyncKeyCallback callback) { |
| 253 | + [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterUIPressProxy* press, |
| 254 | + FlutterAsyncKeyCallback callback) { |
246 | 255 | callbackCount++; |
247 | 256 | callback(primaryResponse); |
248 | 257 | }]]; |
@@ -291,4 +300,73 @@ - (void)testSingleSecondaryResponder API_AVAILABLE(ios(13.4)) { |
291 | 300 | XCTAssertFalse(completeHandled); |
292 | 301 | } |
293 | 302 |
|
| 303 | +- (void)testEventsProcessedSequentially API_AVAILABLE(ios(13.4)) { |
| 304 | + constexpr UIKeyboardHIDUsage keyId1 = (UIKeyboardHIDUsage)0x50; |
| 305 | + constexpr UIKeyboardHIDUsage keyId2 = (UIKeyboardHIDUsage)0x51; |
| 306 | + FlutterUIPressProxy* event1 = keyDownEvent(keyId1); |
| 307 | + FlutterUIPressProxy* event2 = keyDownEvent(keyId2); |
| 308 | + __block FlutterAsyncKeyCallback key1Callback; |
| 309 | + __block FlutterAsyncKeyCallback key2Callback; |
| 310 | + __block bool key1Handled = true; |
| 311 | + __block bool key2Handled = true; |
| 312 | + |
| 313 | + FlutterKeyboardManager* manager = [[FlutterKeyboardManager alloc] init]; |
| 314 | + [manager addPrimaryResponder:[self mockPrimaryResponder:^(FlutterUIPressProxy* press, |
| 315 | + FlutterAsyncKeyCallback callback) { |
| 316 | + if (press == event1) { |
| 317 | + key1Callback = callback; |
| 318 | + } else if (press == event2) { |
| 319 | + key2Callback = callback; |
| 320 | + } |
| 321 | + }]]; |
| 322 | + |
| 323 | + // Add both presses into the main CFRunLoop queue |
| 324 | + CFRunLoopTimerRef timer0 = CFRunLoopTimerCreateWithHandler( |
| 325 | + kCFAllocatorDefault, CFAbsoluteTimeGetCurrent(), 0, 0, 0, ^(CFRunLoopTimerRef timerRef) { |
| 326 | + [manager handlePress:event1 |
| 327 | + nextAction:^() { |
| 328 | + key1Handled = false; |
| 329 | + }]; |
| 330 | + }); |
| 331 | + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer0, kCFRunLoopCommonModes); |
| 332 | + CFRunLoopTimerRef timer1 = CFRunLoopTimerCreateWithHandler( |
| 333 | + kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + 1, 0, 0, 0, ^(CFRunLoopTimerRef timerRef) { |
| 334 | + // key1 should be completely finished by now |
| 335 | + XCTAssertFalse(key1Handled); |
| 336 | + [manager handlePress:event2 |
| 337 | + nextAction:^() { |
| 338 | + key2Handled = false; |
| 339 | + }]; |
| 340 | + // End the nested CFRunLoop |
| 341 | + CFRunLoopStop(CFRunLoopGetCurrent()); |
| 342 | + }); |
| 343 | + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer1, kCFRunLoopCommonModes); |
| 344 | + |
| 345 | + // Add the callbacks to the CFRunLoop with mode kMessageLoopCFRunLoopMode |
| 346 | + // This allows them to interrupt the loop started within handlePress |
| 347 | + CFRunLoopTimerRef timer2 = CFRunLoopTimerCreateWithHandler( |
| 348 | + kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + 2, 0, 0, 0, ^(CFRunLoopTimerRef timerRef) { |
| 349 | + // No processing should be done on key2 yet |
| 350 | + XCTAssertTrue(key1Callback != nil); |
| 351 | + XCTAssertTrue(key2Callback == nil); |
| 352 | + key1Callback(false); |
| 353 | + }); |
| 354 | + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer2, |
| 355 | + fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode); |
| 356 | + CFRunLoopTimerRef timer3 = CFRunLoopTimerCreateWithHandler( |
| 357 | + kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + 3, 0, 0, 0, ^(CFRunLoopTimerRef timerRef) { |
| 358 | + // Both keys should be processed by now |
| 359 | + XCTAssertTrue(key1Callback != nil); |
| 360 | + XCTAssertTrue(key2Callback != nil); |
| 361 | + key2Callback(false); |
| 362 | + }); |
| 363 | + CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer3, |
| 364 | + fml::MessageLoopDarwin::kMessageLoopCFRunLoopMode); |
| 365 | + |
| 366 | + // Start a nested CFRunLoop so we can wait for both presses to complete before exiting the test |
| 367 | + CFRunLoopRun(); |
| 368 | + XCTAssertFalse(key2Handled); |
| 369 | + XCTAssertFalse(key1Handled); |
| 370 | +} |
| 371 | + |
294 | 372 | @end |
0 commit comments