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#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h" 8 9#include "FlutterBinaryMessenger.h" 10 11#if !__has_feature(objc_arc) 12#error ARC must be enabled! 13#endif 14 15@interface FlutterViewControllerTest : XCTestCase 16@end 17 18// The following conditional compilation defines an API 13 concept on earlier API targets so that 19// a compiler compiling against API 12 or below does not blow up due to non-existent members. 20#if __IPHONE_OS_VERSION_MAX_ALLOWED < 130000 21typedef enum UIAccessibilityContrast : NSInteger { 22 UIAccessibilityContrastUnspecified = 0, 23 UIAccessibilityContrastNormal = 1, 24 UIAccessibilityContrastHigh = 2 25} UIAccessibilityContrast; 26 27@interface UITraitCollection (MethodsFromNewerSDK) 28- (UIAccessibilityContrast)accessibilityContrast; 29@end 30#endif 31 32@implementation FlutterViewControllerTest 33 34- (void)testBinaryMessenger { 35 id engine = OCMClassMock([FlutterEngine class]); 36 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine 37 nibName:nil 38 bundle:nil]; 39 XCTAssertNotNil(vc); 40 id messenger = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); 41 OCMStub([engine binaryMessenger]).andReturn(messenger); 42 XCTAssertEqual(vc.binaryMessenger, messenger); 43 OCMVerify([engine binaryMessenger]); 44} 45 46#pragma mark - Platform Brightness 47 48- (void)testItReportsLightPlatformBrightnessByDefault { 49 // Setup test. 50 id engine = OCMClassMock([FlutterEngine class]); 51 52 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); 53 OCMStub([engine settingsChannel]).andReturn(settingsChannel); 54 55 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine 56 nibName:nil 57 bundle:nil]; 58 59 // Exercise behavior under test. 60 [vc traitCollectionDidChange:nil]; 61 62 // Verify behavior. 63 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { 64 return [message[@"platformBrightness"] isEqualToString:@"light"]; 65 }]]); 66 67 // Clean up mocks 68 [engine stopMocking]; 69 [settingsChannel stopMocking]; 70} 71 72- (void)testItReportsPlatformBrightnessWhenViewWillAppear { 73 // Setup test. 74 id engine = OCMClassMock([FlutterEngine class]); 75 76 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); 77 OCMStub([engine settingsChannel]).andReturn(settingsChannel); 78 79 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine 80 nibName:nil 81 bundle:nil]; 82 83 // Exercise behavior under test. 84 [vc viewWillAppear:false]; 85 86 // Verify behavior. 87 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { 88 return [message[@"platformBrightness"] isEqualToString:@"light"]; 89 }]]); 90 91 // Clean up mocks 92 [engine stopMocking]; 93 [settingsChannel stopMocking]; 94} 95 96- (void)testItReportsDarkPlatformBrightnessWhenTraitCollectionRequestsIt { 97 if (!@available(iOS 13, *)) { 98 return; 99 } 100 101 // Setup test. 102 id engine = OCMClassMock([FlutterEngine class]); 103 104 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); 105 OCMStub([engine settingsChannel]).andReturn(settingsChannel); 106 107 FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine 108 nibName:nil 109 bundle:nil]; 110 id mockTraitCollection = 111 [self fakeTraitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark]; 112 113 // We partially mock the real FlutterViewController to act as the OS and report 114 // the UITraitCollection of our choice. Mocking the object under test is not 115 // desirable, but given that the OS does not offer a DI approach to providing 116 // our own UITraitCollection, this seems to be the least bad option. 117 id partialMockVC = OCMPartialMock(realVC); 118 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection); 119 120 // Exercise behavior under test. 121 [partialMockVC traitCollectionDidChange:nil]; 122 123 // Verify behavior. 124 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { 125 return [message[@"platformBrightness"] isEqualToString:@"dark"]; 126 }]]); 127 128 // Clean up mocks 129 [partialMockVC stopMocking]; 130 [engine stopMocking]; 131 [settingsChannel stopMocking]; 132 [mockTraitCollection stopMocking]; 133} 134 135// Creates a mocked UITraitCollection with nil values for everything except userInterfaceStyle, 136// which is set to the given "style". 137- (UITraitCollection*)fakeTraitCollectionWithUserInterfaceStyle:(UIUserInterfaceStyle)style { 138 id mockTraitCollection = OCMClassMock([UITraitCollection class]); 139 OCMStub([mockTraitCollection userInterfaceStyle]).andReturn(style); 140 return mockTraitCollection; 141} 142 143#pragma mark - Platform Contrast 144 145- (void)testItReportsNormalPlatformContrastByDefault { 146 if (!@available(iOS 13, *)) { 147 return; 148 } 149 150 // Setup test. 151 id engine = OCMClassMock([FlutterEngine class]); 152 153 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); 154 OCMStub([engine settingsChannel]).andReturn(settingsChannel); 155 156 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine 157 nibName:nil 158 bundle:nil]; 159 160 // Exercise behavior under test. 161 [vc traitCollectionDidChange:nil]; 162 163 // Verify behavior. 164 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { 165 return [message[@"platformContrast"] isEqualToString:@"normal"]; 166 }]]); 167 168 // Clean up mocks 169 [engine stopMocking]; 170 [settingsChannel stopMocking]; 171} 172 173- (void)testItReportsPlatformContrastWhenViewWillAppear { 174 if (!@available(iOS 13, *)) { 175 return; 176 } 177 178 // Setup test. 179 id engine = OCMClassMock([FlutterEngine class]); 180 181 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); 182 OCMStub([engine settingsChannel]).andReturn(settingsChannel); 183 184 FlutterViewController* vc = [[FlutterViewController alloc] initWithEngine:engine 185 nibName:nil 186 bundle:nil]; 187 188 // Exercise behavior under test. 189 [vc viewWillAppear:false]; 190 191 // Verify behavior. 192 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { 193 return [message[@"platformContrast"] isEqualToString:@"normal"]; 194 }]]); 195 196 // Clean up mocks 197 [engine stopMocking]; 198 [settingsChannel stopMocking]; 199} 200 201- (void)testItReportsHighContrastWhenTraitCollectionRequestsIt { 202 if (!@available(iOS 13, *)) { 203 return; 204 } 205 206 // Setup test. 207 id engine = OCMClassMock([FlutterEngine class]); 208 209 id settingsChannel = OCMClassMock([FlutterBasicMessageChannel class]); 210 OCMStub([engine settingsChannel]).andReturn(settingsChannel); 211 212 FlutterViewController* realVC = [[FlutterViewController alloc] initWithEngine:engine 213 nibName:nil 214 bundle:nil]; 215 id mockTraitCollection = [self fakeTraitCollectionWithContrast:UIAccessibilityContrastHigh]; 216 217 // We partially mock the real FlutterViewController to act as the OS and report 218 // the UITraitCollection of our choice. Mocking the object under test is not 219 // desirable, but given that the OS does not offer a DI approach to providing 220 // our own UITraitCollection, this seems to be the least bad option. 221 id partialMockVC = OCMPartialMock(realVC); 222 OCMStub([partialMockVC traitCollection]).andReturn(mockTraitCollection); 223 224 // Exercise behavior under test. 225 [partialMockVC traitCollectionDidChange:mockTraitCollection]; 226 227 // Verify behavior. 228 OCMVerify([settingsChannel sendMessage:[OCMArg checkWithBlock:^BOOL(id message) { 229 return [message[@"platformContrast"] isEqualToString:@"high"]; 230 }]]); 231 232 // Clean up mocks 233 [partialMockVC stopMocking]; 234 [engine stopMocking]; 235 [settingsChannel stopMocking]; 236 [mockTraitCollection stopMocking]; 237} 238 239// Creates a mocked UITraitCollection with nil values for everything except accessibilityContrast, 240// which is set to the given "contrast". 241- (UITraitCollection*)fakeTraitCollectionWithContrast:(UIAccessibilityContrast)contrast { 242 id mockTraitCollection = OCMClassMock([UITraitCollection class]); 243 OCMStub([mockTraitCollection accessibilityContrast]).andReturn(contrast); 244 return mockTraitCollection; 245} 246 247@end 248