Flutter Engine
FlutterViewControllerTest.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 <OCMock/OCMock.h>
6 #import <XCTest/XCTest.h>
7 
8 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterBinaryMessenger.h"
9 #import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterViewController_Internal.h"
12 
14 
15 @interface FlutterEngine ()
16 - (BOOL)createShell:(NSString*)entrypoint
17  libraryURI:(NSString*)libraryURI
18  initialRoute:(NSString*)initialRoute;
19 @end
20 
22 - (void)notifyLowMemory;
23 @end
24 
25 extern NSNotificationName const FlutterViewControllerWillDealloc;
26 
27 /// A simple mock class for FlutterEngine.
28 ///
29 /// OCMockClass can't be used for FlutterEngine sometimes because OCMock retains arguments to
30 /// invocations and since the init for FlutterViewController calls a method on the
31 /// FlutterEngine it creates a retain cycle that stops us from testing behaviors related to
32 /// deleting FlutterViewControllers.
33 @interface MockEngine : NSObject
34 @end
35 
36 @implementation MockEngine
37 - (FlutterViewController*)viewController {
38  return nil;
39 }
40 - (void)setViewController:(FlutterViewController*)viewController {
41  // noop
42 }
43 @end
44 
45 @interface FlutterViewControllerTest : XCTestCase
46 @end
47 
48 // The following conditional compilation defines an API 13 concept on earlier API targets so that
49 // a compiler compiling against API 12 or below does not blow up due to non-existent members.
50 #if __IPHONE_OS_VERSION_MAX_ALLOWED < 130000
51 typedef enum UIAccessibilityContrast : NSInteger {
56 
59 @end
60 #endif
61 
63 - (void)surfaceUpdated:(BOOL)appeared;
64 - (void)performOrientationUpdate:(UIInterfaceOrientationMask)new_preferences;
65 - (void)dispatchPresses:(NSSet<UIPress*>*)presses;
66 @end
67 
68 @implementation FlutterViewControllerTest
69 
70 - (void)testViewDidDisappearDoesntPauseEngineWhenNotTheViewController {
71  id engine = OCMClassMock([FlutterEngine class]);
72  id lifecycleChannel = OCMClassMock([FlutterBasicMessageChannel class]);
73  OCMStub([engine lifecycleChannel]).andReturn(lifecycleChannel);
74  FlutterViewController* viewControllerA = [[FlutterViewController alloc] initWithEngine:engine
75  nibName:nil
76  bundle:nil];
77  FlutterViewController* viewControllerB = [[FlutterViewController alloc] initWithEngine:engine
78  nibName:nil
79  bundle:nil];
80  id viewControllerMock = OCMPartialMock(viewControllerA);
81  OCMStub([viewControllerMock surfaceUpdated:NO]);
82  OCMStub([engine viewController]).andReturn(viewControllerB);
83  [viewControllerA viewDidDisappear:NO];
84  OCMReject([lifecycleChannel sendMessage:@"AppLifecycleState.paused"]);
85  OCMReject([viewControllerMock surfaceUpdated:[OCMArg any]]);
86 }
87 
88 - (void)testViewDidDisappearDoesPauseEngineWhenIsTheViewController {
89  id engine = OCMClassMock([FlutterEngine class]);
90  id lifecycleChannel = OCMClassMock([FlutterBasicMessageChannel class]);
91  OCMStub([engine lifecycleChannel]).andReturn(lifecycleChannel);
92  FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
93  nibName:nil
94  bundle:nil];
95  id viewControllerMock = OCMPartialMock(viewController);
96  OCMStub([viewControllerMock surfaceUpdated:NO]);
97  OCMStub([engine viewController]).andReturn(viewController);
98  [viewController viewDidDisappear:NO];
99  OCMVerify([lifecycleChannel sendMessage:@"AppLifecycleState.paused"]);
100  OCMVerify([viewControllerMock surfaceUpdated:NO]);
101 }
102 
103 - (void)testBinaryMessenger {
104  id engine = OCMClassMock([FlutterEngine class]);
105  FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
106  nibName:nil
107  bundle:nil];
108  XCTAssertNotNil(vc);
109  id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger));
110  OCMStub([engine binaryMessenger]).andReturn(messenger);
111  XCTAssertEqual(vc.binaryMessenger, messenger);
112  OCMVerify([engine binaryMessenger]);
113 }
114 
115 #pragma mark - Platform Brightness
116 
117 - (void)testItReportsLightPlatformBrightnessByDefault {
118  // Setup test.
119  id engine = OCMClassMock([FlutterEngine class]);
120 
121  id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
122  OCMStub([engine settingsChannel]).andReturn(settingsChannel);
123 
124  FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
125  nibName:nil
126  bundle:nil];
127 
128  // Exercise behavior under test.
129  [vc traitCollectionDidChange:nil];
130 
131  // Verify behavior.
132  OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
133  return [message[@"platformBrightness"] isEqualToString:@"light"];
134  }]]);
135 
136  // Clean up mocks
137  [engine stopMocking];
138  [settingsChannel stopMocking];
139 }
140 
141 - (void)testItReportsPlatformBrightnessWhenViewWillAppear {
142  // Setup test.
143  id engine = OCMClassMock([FlutterEngine class]);
144 
145  id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
146  OCMStub([engine settingsChannel]).andReturn(settingsChannel);
147 
148  FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
149  nibName:nil
150  bundle:nil];
151 
152  // Exercise behavior under test.
153  [vc viewWillAppear:false];
154 
155  // Verify behavior.
156  OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
157  return [message[@"platformBrightness"] isEqualToString:@"light"];
158  }]]);
159 
160  // Clean up mocks
161  [engine stopMocking];
162  [settingsChannel stopMocking];
163 }
164 
165 - (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt {
166  if (@available(iOS 13, *)) {
167  // noop
168  } else {
169  return;
170  }
171 
172  // Setup test.
173  id engine = OCMClassMock([FlutterEngine class]);
174 
175  id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
176  OCMStub([engine settingsChannel]).andReturn(settingsChannel);
177 
178  FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine
179  nibName:nil
180  bundle:nil];
181  id mockTraitCollection =
182  [self fakeTraitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
183 
184  // We partially mock the real FlutterViewController to act as the OS and report
185  // the UITraitCollection of our choice. Mocking the object under test is not
186  // desirable, but given that the OS does not offer a DI approach to providing
187  // our own UITraitCollection, this seems to be the least bad option.
188  id partialMockVC = OCMPartialMock(realVC);
189  OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
190 
191  // Exercise behavior under test.
192  [partialMockVC traitCollectionDidChange:nil];
193 
194  // Verify behavior.
195  OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
196  return [message[@"platformBrightness"] isEqualToString:@"dark"];
197  }]]);
198 
199  // Clean up mocks
200  [partialMockVC stopMocking];
201  [engine stopMocking];
202  [settingsChannel stopMocking];
203  [mockTraitCollection stopMocking];
204 }
205 
206 // Creates a mocked UITraitCollection with nil values for everything except userInterfaceStyle,
207 // which is set to the given "style".
208 - (UITraitCollection*)fakeTraitCollectionWithUserInterfaceStyle:(UIUserInterfaceStyle)style {
209  id mockTraitCollection = OCMClassMock([UITraitCollection class]);
210  OCMStub([mockTraitCollection userInterfaceStyle]).andReturn(style);
211  return mockTraitCollection;
212 }
213 
214 #pragma mark - Platform Contrast
215 
216 - (void)testItReportsNormalPlatformContrastByDefault {
217  if (@available(iOS 13, *)) {
218  // noop
219  } else {
220  return;
221  }
222 
223  // Setup test.
224  id engine = OCMClassMock([FlutterEngine class]);
225 
226  id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
227  OCMStub([engine settingsChannel]).andReturn(settingsChannel);
228 
229  FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
230  nibName:nil
231  bundle:nil];
232 
233  // Exercise behavior under test.
234  [vc traitCollectionDidChange:nil];
235 
236  // Verify behavior.
237  OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
238  return [message[@"platformContrast"] isEqualToString:@"normal"];
239  }]]);
240 
241  // Clean up mocks
242  [engine stopMocking];
243  [settingsChannel stopMocking];
244 }
245 
246 - (void)testItReportsPlatformContrastWhenViewWillAppear {
247  if (@available(iOS 13, *)) {
248  // noop
249  } else {
250  return;
251  }
252 
253  // Setup test.
254  id engine = OCMClassMock([FlutterEngine class]);
255 
256  id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
257  OCMStub([engine settingsChannel]).andReturn(settingsChannel);
258 
259  FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
260  nibName:nil
261  bundle:nil];
262 
263  // Exercise behavior under test.
264  [vc viewWillAppear:false];
265 
266  // Verify behavior.
267  OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
268  return [message[@"platformContrast"] isEqualToString:@"normal"];
269  }]]);
270 
271  // Clean up mocks
272  [engine stopMocking];
273  [settingsChannel stopMocking];
274 }
275 
276 - (void)testItReportsHighContrastWhenTraitCollectionRequestsIt {
277  if (@available(iOS 13, *)) {
278  // noop
279  } else {
280  return;
281  }
282 
283  // Setup test.
284  id engine = OCMClassMock([FlutterEngine class]);
285 
286  id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]);
287  OCMStub([engine settingsChannel]).andReturn(settingsChannel);
288 
289  FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine
290  nibName:nil
291  bundle:nil];
292  id mockTraitCollection = [self fakeTraitCollectionWithContrast:UIAccessibilityContrastHigh];
293 
294  // We partially mock the real FlutterViewController to act as the OS and report
295  // the UITraitCollection of our choice. Mocking the object under test is not
296  // desirable, but given that the OS does not offer a DI approach to providing
297  // our own UITraitCollection, this seems to be the least bad option.
298  id partialMockVC = OCMPartialMock(realVC);
299  OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection);
300 
301  // Exercise behavior under test.
302  [partialMockVC traitCollectionDidChange:mockTraitCollection];
303 
304  // Verify behavior.
305  OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
306  return [message[@"platformContrast"] isEqualToString:@"high"];
307  }]]);
308 
309  // Clean up mocks
310  [partialMockVC stopMocking];
311  [engine stopMocking];
312  [settingsChannel stopMocking];
313  [mockTraitCollection stopMocking];
314 }
315 
316 - (void)testPerformOrientationUpdateForcesOrientationChange {
317  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
318  currentOrientation:UIInterfaceOrientationLandscapeLeft
319  didChangeOrientation:YES
320  resultingOrientation:UIInterfaceOrientationPortrait];
321 
322  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
323  currentOrientation:UIInterfaceOrientationLandscapeRight
324  didChangeOrientation:YES
325  resultingOrientation:UIInterfaceOrientationPortrait];
326 
327  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
328  currentOrientation:UIInterfaceOrientationPortraitUpsideDown
329  didChangeOrientation:YES
330  resultingOrientation:UIInterfaceOrientationPortrait];
331 
332  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
333  currentOrientation:UIInterfaceOrientationLandscapeLeft
334  didChangeOrientation:YES
335  resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
336 
337  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
338  currentOrientation:UIInterfaceOrientationLandscapeRight
339  didChangeOrientation:YES
340  resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
341 
342  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
343  currentOrientation:UIInterfaceOrientationPortrait
344  didChangeOrientation:YES
345  resultingOrientation:UIInterfaceOrientationPortraitUpsideDown];
346 
347  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
348  currentOrientation:UIInterfaceOrientationPortrait
349  didChangeOrientation:YES
350  resultingOrientation:UIInterfaceOrientationLandscapeLeft];
351 
352  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
353  currentOrientation:UIInterfaceOrientationPortraitUpsideDown
354  didChangeOrientation:YES
355  resultingOrientation:UIInterfaceOrientationLandscapeLeft];
356 
357  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
358  currentOrientation:UIInterfaceOrientationPortrait
359  didChangeOrientation:YES
360  resultingOrientation:UIInterfaceOrientationLandscapeLeft];
361 
362  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
363  currentOrientation:UIInterfaceOrientationLandscapeRight
364  didChangeOrientation:YES
365  resultingOrientation:UIInterfaceOrientationLandscapeLeft];
366 
367  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
368  currentOrientation:UIInterfaceOrientationPortraitUpsideDown
369  didChangeOrientation:YES
370  resultingOrientation:UIInterfaceOrientationLandscapeLeft];
371 
372  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
373  currentOrientation:UIInterfaceOrientationPortrait
374  didChangeOrientation:YES
375  resultingOrientation:UIInterfaceOrientationLandscapeRight];
376 
377  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
378  currentOrientation:UIInterfaceOrientationLandscapeLeft
379  didChangeOrientation:YES
380  resultingOrientation:UIInterfaceOrientationLandscapeRight];
381 
382  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
383  currentOrientation:UIInterfaceOrientationPortraitUpsideDown
384  didChangeOrientation:YES
385  resultingOrientation:UIInterfaceOrientationLandscapeRight];
386 
387  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
388  currentOrientation:UIInterfaceOrientationPortraitUpsideDown
389  didChangeOrientation:YES
390  resultingOrientation:UIInterfaceOrientationPortrait];
391 }
392 
393 - (void)testPerformOrientationUpdateDoesNotForceOrientationChange {
394  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
395  currentOrientation:UIInterfaceOrientationPortrait
396  didChangeOrientation:NO
397  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
398 
399  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
400  currentOrientation:UIInterfaceOrientationPortraitUpsideDown
401  didChangeOrientation:NO
402  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
403 
404  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
405  currentOrientation:UIInterfaceOrientationLandscapeLeft
406  didChangeOrientation:NO
407  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
408 
409  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAll
410  currentOrientation:UIInterfaceOrientationLandscapeRight
411  didChangeOrientation:NO
412  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
413 
414  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
415  currentOrientation:UIInterfaceOrientationPortrait
416  didChangeOrientation:NO
417  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
418 
419  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
420  currentOrientation:UIInterfaceOrientationLandscapeLeft
421  didChangeOrientation:NO
422  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
423 
424  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskAllButUpsideDown
425  currentOrientation:UIInterfaceOrientationLandscapeRight
426  didChangeOrientation:NO
427  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
428 
429  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortrait
430  currentOrientation:UIInterfaceOrientationPortrait
431  didChangeOrientation:NO
432  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
433 
434  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskPortraitUpsideDown
435  currentOrientation:UIInterfaceOrientationPortraitUpsideDown
436  didChangeOrientation:NO
437  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
438 
439  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
440  currentOrientation:UIInterfaceOrientationLandscapeLeft
441  didChangeOrientation:NO
442  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
443 
444  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscape
445  currentOrientation:UIInterfaceOrientationLandscapeRight
446  didChangeOrientation:NO
447  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
448 
449  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeLeft
450  currentOrientation:UIInterfaceOrientationLandscapeLeft
451  didChangeOrientation:NO
452  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
453 
454  [self orientationTestWithOrientationUpdate:UIInterfaceOrientationMaskLandscapeRight
455  currentOrientation:UIInterfaceOrientationLandscapeRight
456  didChangeOrientation:NO
457  resultingOrientation:static_cast<UIInterfaceOrientation>(0)];
458 }
459 
460 // Perform an orientation update test that fails when the expected outcome
461 // for an orientation update is not met
462 - (void)orientationTestWithOrientationUpdate:(UIInterfaceOrientationMask)mask
463  currentOrientation:(UIInterfaceOrientation)currentOrientation
464  didChangeOrientation:(BOOL)didChange
465  resultingOrientation:(UIInterfaceOrientation)resultingOrientation {
466  id engine = OCMClassMock([FlutterEngine class]);
467 
468  id deviceMock = OCMPartialMock([UIDevice currentDevice]);
469  if (!didChange) {
470  OCMReject([deviceMock setValue:[OCMArg any] forKey:@"orientation"]);
471  } else {
472  OCMExpect([deviceMock setValue:@(resultingOrientation) forKey:@"orientation"]);
473  }
474 
475  FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine
476  nibName:nil
477  bundle:nil];
478  id mockApplication = OCMClassMock([UIApplication class]);
479  OCMStub([mockApplication sharedApplication]).andReturn(mockApplication);
480  OCMStub([mockApplication statusBarOrientation]).andReturn(currentOrientation);
481 
482  [realVC performOrientationUpdate:mask];
483  OCMVerifyAll(deviceMock);
484  [engine stopMocking];
485  [deviceMock stopMocking];
486  [mockApplication stopMocking];
487 }
488 
489 // Creates a mocked UITraitCollection with nil values for everything except accessibilityContrast,
490 // which is set to the given "contrast".
491 - (UITraitCollection*)fakeTraitCollectionWithContrast:(UIAccessibilityContrast)contrast {
492  id mockTraitCollection = OCMClassMock([UITraitCollection class]);
493  OCMStub([mockTraitCollection accessibilityContrast]).andReturn(contrast);
494  return mockTraitCollection;
495 }
496 
497 - (void)testWillDeallocNotification {
498  XCTestExpectation* expectation =
499  [[XCTestExpectation alloc] initWithDescription:@"notification called"];
500  id engine = [[MockEngine alloc] init];
501  @autoreleasepool {
502  FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine
503  nibName:nil
504  bundle:nil];
505  [[NSNotificationCenter defaultCenter] addObserverForName:FlutterViewControllerWillDealloc
506  object:nil
507  queue:[NSOperationQueue mainQueue]
508  usingBlock:^(NSNotification* _Nonnull note) {
509  [expectation fulfill];
510  }];
511  realVC = nil;
512  }
513  [self waitForExpectations:@[ expectation ] timeout:1.0];
514 }
515 
516 - (void)testDoesntLoadViewInInit {
517  FlutterDartProject* project = [[FlutterDartProject alloc] init];
518  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
519  [engine createShell:@"" libraryURI:@"" initialRoute:nil];
520  FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine
521  nibName:nil
522  bundle:nil];
523  XCTAssertFalse([realVC isViewLoaded], @"shouldn't have loaded since it hasn't been shown");
524 }
525 
526 - (void)testHideOverlay {
527  FlutterDartProject* project = [[FlutterDartProject alloc] init];
528  FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar" project:project];
529  [engine createShell:@"" libraryURI:@"" initialRoute:nil];
530  FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine
531  nibName:nil
532  bundle:nil];
533  XCTAssertFalse(realVC.prefersHomeIndicatorAutoHidden, @"");
534  [[NSNotificationCenter defaultCenter] postNotificationName:FlutterViewControllerHideHomeIndicator
535  object:nil];
536  XCTAssertTrue(realVC.prefersHomeIndicatorAutoHidden, @"");
537 }
538 
539 - (void)testNotifyLowMemory {
540  id engine = OCMClassMock([FlutterEngine class]);
541  FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engine
542  nibName:nil
543  bundle:nil];
544  OCMStub([engine viewController]).andReturn(viewController);
545  id viewControllerMock = OCMPartialMock(viewController);
546  OCMStub([viewControllerMock surfaceUpdated:NO]);
547 
548  [viewController beginAppearanceTransition:NO animated:NO];
549  [viewController endAppearanceTransition];
550  OCMVerify([engine notifyLowMemory]);
551 }
552 
553 - (void)testValidKeyUpEvent API_AVAILABLE(ios(13.4)) {
554  if (@available(iOS 13.4, *)) {
555  // noop
556  } else {
557  return;
558  }
559 
560  id engine = OCMClassMock([FlutterEngine class]);
561 
562  id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
563  OCMStub([engine keyEventChannel]).andReturn(keyEventChannel);
564 
565  FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
566  nibName:nil
567  bundle:nil];
568 
569  id testSet = [self fakeUiPressSetForPhase:UIPressPhaseBegan
570  keyCode:UIKeyboardHIDUsageKeyboardA
571  modifierFlags:UIKeyModifierShift
572  characters:@"a"
573  charactersIgnoringModifiers:@"A"];
574 
575  // Exercise behavior under test.
576  [vc dispatchPresses:testSet];
577 
578  // Verify behavior.
579  OCMVerify([keyEventChannel
580  sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
581  return [message[@"keymap"] isEqualToString:@"ios"] &&
582  [message[@"type"] isEqualToString:@"keydown"] &&
583  [message[@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]] &&
584  [message[@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:131072]] &&
585  [message[@"characters"] isEqualToString:@"a"] &&
586  [message[@"charactersIgnoringModifiers"] isEqualToString:@"A"];
587  }]]);
588 
589  // Clean up mocks
590  [engine stopMocking];
591  [keyEventChannel stopMocking];
592 }
593 
594 - (void)testValidKeyDownEvent API_AVAILABLE(ios(13.4)) {
595  if (@available(iOS 13.4, *)) {
596  // noop
597  } else {
598  return;
599  }
600 
601  id engine = OCMClassMock([FlutterEngine class]);
602 
603  id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
604  OCMStub([engine keyEventChannel]).andReturn(keyEventChannel);
605 
606  FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
607  nibName:nil
608  bundle:nil];
609 
610  id testSet = [self fakeUiPressSetForPhase:UIPressPhaseEnded
611  keyCode:UIKeyboardHIDUsageKeyboardA
612  modifierFlags:UIKeyModifierShift
613  characters:@"a"
614  charactersIgnoringModifiers:@"A"];
615 
616  // Exercise behavior under test.
617  [vc dispatchPresses:testSet];
618 
619  // Verify behavior.
620  OCMVerify([keyEventChannel
621  sendMessage:[OCMArg checkWithBlock:^BOOL(id message) {
622  return [message[@"keymap"] isEqualToString:@"ios"] &&
623  [message[@"type"] isEqualToString:@"keyup"] &&
624  [message[@"keyCode"] isEqualToNumber:[NSNumber numberWithInt:4]] &&
625  [message[@"modifiers"] isEqualToNumber:[NSNumber numberWithInt:131072]] &&
626  [message[@"characters"] isEqualToString:@"a"] &&
627  [message[@"charactersIgnoringModifiers"] isEqualToString:@"A"];
628  }]]);
629 
630  // Clean up mocks
631  [engine stopMocking];
632  [keyEventChannel stopMocking];
633 }
634 
635 - (void)testIgnoredKeyEvents API_AVAILABLE(ios(13.4)) {
636  if (@available(iOS 13.4, *)) {
637  // noop
638  } else {
639  return;
640  }
641 
642  id engine = OCMClassMock([FlutterEngine class]);
643 
644  id keyEventChannel = OCMClassMock([FlutterBasicMessageChannel class]);
645  OCMStub([engine keyEventChannel]).andReturn(keyEventChannel);
646 
647  FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine
648  nibName:nil
649  bundle:nil];
650 
651  id emptySet = [NSSet set];
652  id ignoredSet = [self fakeUiPressSetForPhase:UIPressPhaseStationary
653  keyCode:UIKeyboardHIDUsageKeyboardA
654  modifierFlags:UIKeyModifierShift
655  characters:@"a"
656  charactersIgnoringModifiers:@"A"];
657 
658  id mockUiPress = OCMClassMock([UIPress class]);
659  OCMStub([mockUiPress phase]).andReturn(UIPressPhaseBegan);
660  id emptyKeySet = [NSSet setWithArray:@[ mockUiPress ]];
661  // Exercise behavior under test.
662  [vc dispatchPresses:emptySet];
663  [vc dispatchPresses:ignoredSet];
664  [vc dispatchPresses:emptyKeySet];
665 
666  // Verify behavior.
667  OCMVerify(never(), [keyEventChannel sendMessage:[OCMArg any]]);
668 
669  // Clean up mocks
670  [engine stopMocking];
671  [keyEventChannel stopMocking];
672 }
673 
674 - (NSSet<UIPress*>*)fakeUiPressSetForPhase:(UIPressPhase)phase
675  keyCode:(UIKeyboardHIDUsage)keyCode
676  modifierFlags:(UIKeyModifierFlags)modifierFlags
677  characters:(NSString*)characters
678  charactersIgnoringModifiers:(NSString*)charactersIgnoringModifiers
679  API_AVAILABLE(ios(13.4)) {
680  if (@available(iOS 13.4, *)) {
681  // noop
682  } else {
683  return [NSSet set];
684  }
685  id mockUiPress = OCMClassMock([UIPress class]);
686  OCMStub([mockUiPress phase]).andReturn(phase);
687 
688  id mockUiKey = OCMClassMock([UIKey class]);
689  OCMStub([mockUiKey keyCode]).andReturn(keyCode);
690  OCMStub([mockUiKey modifierFlags]).andReturn(modifierFlags);
691  OCMStub([mockUiKey characters]).andReturn(characters);
692  OCMStub([mockUiKey charactersIgnoringModifiers]).andReturn(charactersIgnoringModifiers);
693 
694  OCMStub([mockUiPress key]).andReturn(mockUiKey);
695 
696  return [NSSet setWithArray:@[ mockUiPress ]];
697 }
698 
699 @end
NSNotificationName const FlutterViewControllerWillDealloc
NSObject< FlutterBinaryMessenger > * binaryMessenger
FlutterPointerPhase phase
Definition: fl_view.cc:78
UIAccessibilityContrast accessibilityContrast()
UIAccessibilityContrast
#define FLUTTER_ASSERT_ARC
Definition: FlutterMacros.h:44