| Flutter iOS Embedder |

FlutterViewTest.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/ios/framework/Headers/FlutterEngine.h"
9 #import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterSceneDelegate.h"
10 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterOverlayView.h"
11 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Internal.h"
12 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterSceneLifeCycle_Test.h"
13 #import "flutter/shell/platform/darwin/ios/framework/Source/FlutterView.h"
14
15 FLUTTER_ASSERT_ARC
16
17 @interface FlutterView (Testing)
18 - (BOOL)isWideGamutSupported;
19 @end
20
21 @interface FakeDelegate : NSObject <FlutterViewEngineDelegate>
22 @property(nonatomic) BOOL callbackCalled;
23 @end
24
25 @implementation FakeDelegate
26
27 @synthesize platformViewsController = _platformViewsController;
28
29 - (instancetype)init {
30 _callbackCalled = NO;
31 return self;
32 }
33
34 - (flutter::Rasterizer::Screenshot)takeScreenshot:(flutter::Rasterizer::ScreenshotType)type
35 asBase64Encoded:(BOOL)base64Encode {
36 return {};
37 }
38
39 - (void)flutterViewAccessibilityDidCall {
40 _callbackCalled = YES;
41 }
42
43 @end
44
45 @interface FlutterViewTest : XCTestCase
46 @end
47
48 @implementation FlutterViewTest
49
50 - (void)testFlutterViewEnableSemanticsWhenIsAccessibilityElementIsCalled {
51 FakeDelegate* delegate = [[FakeDelegate alloc] init];
52 FlutterView* view = [[FlutterView alloc] initWithDelegate:delegate opaque:NO enableWideGamut:NO];
53 delegate.callbackCalled = NO;
54 XCTAssertFalse(view.isAccessibilityElement);
55 XCTAssertTrue(delegate.callbackCalled);
56 }
57
58 - (void)testFlutterViewBackgroundColorIsNil {
59 FakeDelegate* delegate = [[FakeDelegate alloc] init];
60 FlutterView* view = [[FlutterView alloc] initWithDelegate:delegate opaque:NO enableWideGamut:NO];
61 XCTAssertNil(view.backgroundColor);
62 }
63
64 - (void)testLayerScalesMatchScreenAfterLayoutSubviews {
65 FakeDelegate* delegate = [[FakeDelegate alloc] init];
66 FlutterView* view = [[FlutterView alloc] initWithDelegate:delegate opaque:NO enableWideGamut:NO];
67 view.layer.contentsScale = CGFloat(-99.0);
68 view.layer.rasterizationScale = CGFloat(-99.0);
69 UIScreen* screen = [view screen];
70 XCTAssertNotEqual(view.layer.contentsScale, screen.scale);
71 XCTAssertNotEqual(view.layer.rasterizationScale, screen.scale);
72 [view layoutSubviews];
73 XCTAssertEqual(view.layer.contentsScale, screen.scale);
74 XCTAssertEqual(view.layer.rasterizationScale, screen.scale);
75 }
76
77 - (void)testViewWillMoveToWindow {
78 NSDictionary* mocks = [self createWindowMocks];
79 FlutterView* view = (FlutterView*)mocks[@"view"];
80 id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"];
81 FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
82 (FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
83 id mockEngine = mocks[@"mockEngine"];
84 id mockWindow = mocks[@"mockWindow"];
85
86 [view willMoveToWindow:mockWindow];
87 OCMVerify(times(1), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine]);
88 XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 1.0);
89 }
90
91 - (void)testViewWillMoveToSameWindow {
92 NSDictionary* mocks = [self createWindowMocks];
93 FlutterView* view = (FlutterView*)mocks[@"view"];
94 id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"];
95 FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
96 (FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
97 id mockEngine = mocks[@"mockEngine"];
98 id mockWindow = mocks[@"mockWindow"];
99
100 [view willMoveToWindow:mockWindow];
101 [view willMoveToWindow:mockWindow];
102
103 OCMVerify(times(2), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine]);
104 XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 1.0);
105 }
106
107 - (void)testMultipleViewsWillMoveToSameWindow {
108 NSDictionary* mocks = [self createWindowMocks];
109 FlutterView* view1 = (FlutterView*)mocks[@"view"];
110 id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"];
111 FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
112 (FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
113 id mockEngine1 = mocks[@"mockEngine"];
114 id mockWindow1 = mocks[@"mockWindow"];
115
116 id mockEngine2 = OCMClassMock([FlutterEngine class]);
117 FlutterView* view2 = [[FlutterView alloc] initWithDelegate:mockEngine2
118 opaque:NO
119 enableWideGamut:NO];
120
121 [view1 willMoveToWindow:mockWindow1];
122 [view2 willMoveToWindow:mockWindow1];
123 [view1 willMoveToWindow:mockWindow1];
124 OCMVerify(times(2), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine1]);
125 OCMVerify(times(1), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine2]);
126 XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 2.0);
127 }
128
129 - (void)testMultipleViewsWillMoveToDifferentWindow {
130 NSDictionary* mocks = [self createWindowMocks];
131 FlutterView* view1 = (FlutterView*)mocks[@"view"];
132 id mockLifecycleDelegate1 = mocks[@"mockLifecycleDelegate"];
133 FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate1 =
134 (FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
135 id mockEngine1 = mocks[@"mockEngine"];
136 id mockWindow1 = mocks[@"mockWindow"];
137
138 NSDictionary* mocks2 = [self createWindowMocks];
139 FlutterView* view2 = (FlutterView*)mocks2[@"view"];
140 id mockLifecycleDelegate2 = mocks2[@"mockLifecycleDelegate"];
141 FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate2 =
142 (FlutterPluginSceneLifeCycleDelegate*)mocks2[@"lifecycleDelegate"];
143 id mockEngine2 = mocks2[@"mockEngine"];
144 id mockWindow2 = mocks2[@"mockWindow"];
145
146 [view1 willMoveToWindow:mockWindow1];
147 [view2 willMoveToWindow:mockWindow2];
148 [view1 willMoveToWindow:mockWindow1];
149 OCMVerify(times(2), [mockLifecycleDelegate1 addFlutterManagedEngine:mockEngine1]);
150 OCMVerify(times(1), [mockLifecycleDelegate2 addFlutterManagedEngine:mockEngine2]);
151 XCTAssertEqual(lifecycleDelegate1.flutterManagedEngines.count, 1.0);
152 XCTAssertEqual(lifecycleDelegate2.flutterManagedEngines.count, 1.0);
153 }
154
155 - (void)testViewRemovedFromWindowAndAddedToNewScene {
156 NSDictionary* mocks = [self createWindowMocks];
157 FlutterView* view = (FlutterView*)mocks[@"view"];
158 id mockLifecycleDelegate = mocks[@"mockLifecycleDelegate"];
159 FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
160 (FlutterPluginSceneLifeCycleDelegate*)mocks[@"lifecycleDelegate"];
161 id mockEngine = mocks[@"mockEngine"];
162 id mockWindow = mocks[@"mockWindow"];
163
164 NSDictionary* mocks2 = [self createWindowMocks];
165 id mockWindow2 = mocks2[@"mockWindow"];
166 id mockLifecycleDelegate2 = mocks2[@"mockLifecycleDelegate"];
167 FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate2 =
168 (FlutterPluginSceneLifeCycleDelegate*)mocks2[@"lifecycleDelegate"];
169
170 id mockView = OCMPartialMock(view);
171
172 [mockView willMoveToWindow:mockWindow];
173 OCMVerify(times(1), [mockLifecycleDelegate addFlutterManagedEngine:mockEngine]);
174 XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 1.0);
175
176 OCMStub([mockView window]).andReturn(mockWindow);
177 [mockView willMoveToWindow:nil];
178 XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 1.0);
179
180 OCMStub([mockView window]).andReturn(nil);
181 [mockView willMoveToWindow:mockWindow2];
182 OCMVerify(times(1), [mockLifecycleDelegate removeFlutterManagedEngine:mockEngine]);
183 XCTAssertEqual(lifecycleDelegate.flutterManagedEngines.count, 0.0);
184 OCMVerify(times(1), [mockLifecycleDelegate2 addFlutterManagedEngine:mockEngine]);
185 XCTAssertEqual(lifecycleDelegate2.flutterManagedEngines.count, 1.0);
186 }
187
188 - (NSDictionary*)createWindowMocks {
189 return [self createWindowMocksWithWideGamut:NO];
190 }
191
192 - (NSDictionary*)createWindowMocksWithWideGamut:(BOOL)enableWideGamut {
193 id mockEngine = OCMClassMock([FlutterEngine class]);
194 FlutterView* view = [[FlutterView alloc] initWithDelegate:mockEngine
195 opaque:NO
196 enableWideGamut:enableWideGamut];
197 id mockWindow = OCMClassMock([UIWindow class]);
198 id mockWindowScene = OCMClassMock([UIWindowScene class]);
199
200 FlutterSceneDelegate* sceneDelegate = [[FlutterSceneDelegate alloc] init];
201 id mockSceneDelegate = OCMPartialMock(sceneDelegate);
202
203 FlutterPluginSceneLifeCycleDelegate* lifecycleDelegate =
204 [[FlutterPluginSceneLifeCycleDelegate alloc] init];
205 id mockLifecycleDelegate = OCMPartialMock(lifecycleDelegate);
206
207 OCMStub([mockWindow windowScene]).andReturn(mockWindowScene);
208 OCMStub([mockWindowScene delegate]).andReturn(mockSceneDelegate);
209 OCMStub([mockSceneDelegate sceneLifeCycleDelegate]).andReturn(mockLifecycleDelegate);
210
211 return @{
212 @"view" : view,
213 @"mockLifecycleDelegate" : mockLifecycleDelegate,
214 @"lifecycleDelegate" : lifecycleDelegate,
215 @"mockEngine" : mockEngine,
216 @"mockWindow" : mockWindow,
217 };
218 }
219
220 #pragma mark - Wide Gamut Tests
221
222 // Helper: add FlutterView to a real UIWindow so that layoutSubviews can access screen.
223 - (FlutterView*)createViewInWindowWithWideGamut:(BOOL)enableWideGamut {
224 FakeDelegate* delegate = [[FakeDelegate alloc] init];
225 FlutterView* view = [[FlutterView alloc] initWithDelegate:delegate
226 opaque:NO
227 enableWideGamut:enableWideGamut];
228 // Add to a real window so layoutSubviews has access to screen.
229 UIWindow* window = [[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
230 [window addSubview:view];
231 view.frame = window.bounds;
232 [view layoutSubviews];
233 return view;
234 }
235
236 - (void)testWideGamutViewSetsBGRA10XRPixelFormat {
237 FlutterView* view = [self createViewInWindowWithWideGamut:YES];
238 // On a wide gamut capable device, the pixel format should be BGRA10_XR.
239 // On non-wide-gamut devices, it falls back to BGRA8Unorm.
240 if ([view isWideGamutSupported]) {
241 XCTAssertEqual(view.pixelFormat, MTLPixelFormatBGRA10_XR);
242 } else {
243 XCTAssertEqual(view.pixelFormat, MTLPixelFormatBGRA8Unorm);
244 }
245 }
246
247 - (void)testStandardGamutViewKeepsBGRA8Unorm {
248 FlutterView* view = [self createViewInWindowWithWideGamut:NO];
249 XCTAssertEqual(view.pixelFormat, MTLPixelFormatBGRA8Unorm);
250 }
251
252 - (void)testWideGamutViewSetsExtendedSRGBColorSpace {
253 FlutterView* view = [self createViewInWindowWithWideGamut:YES];
254 if ([view isWideGamutSupported]) {
255 CAMetalLayer* layer = (CAMetalLayer*)view.layer;
256 CGColorSpaceRef colorSpace = layer.colorspace;
257 XCTAssertNotNil((__bridge id)colorSpace);
258 CGColorSpaceRef extendedSRGB = CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB);
259 XCTAssertTrue(CFEqual(colorSpace, extendedSRGB));
260 CGColorSpaceRelease(extendedSRGB);
261 }
262 }
263
264 - (void)testStandardGamutViewDoesNotSetExtendedColorSpace {
265 FlutterView* view = [self createViewInWindowWithWideGamut:NO];
266 CAMetalLayer* layer = (CAMetalLayer*)view.layer;
267 // Default CAMetalLayer colorspace is nil (device default sRGB).
268 XCTAssertNil((__bridge id)layer.colorspace);
269 }
270
271 #pragma mark - FlutterOverlayView Wide Gamut Tests
272
273 - (void)testOverlayViewWideGamutSetsBGRA10XR {
274 FlutterOverlayView* overlay =
275 [[FlutterOverlayView alloc] initWithContentsScale:2.0 pixelFormat:MTLPixelFormatBGRA10_XR];
276 CAMetalLayer* layer = (CAMetalLayer*)overlay.layer;
277 XCTAssertEqual(layer.pixelFormat, MTLPixelFormatBGRA10_XR);
278 }
279
280 - (void)testOverlayViewWideGamutSetsExtendedSRGBColorSpace {
281 FlutterOverlayView* overlay =
282 [[FlutterOverlayView alloc] initWithContentsScale:2.0 pixelFormat:MTLPixelFormatBGRA10_XR];
283 CAMetalLayer* layer = (CAMetalLayer*)overlay.layer;
284 CGColorSpaceRef colorSpace = layer.colorspace;
285 XCTAssertNotNil((__bridge id)colorSpace);
286 CGColorSpaceRef extendedSRGB = CGColorSpaceCreateWithName(kCGColorSpaceExtendedSRGB);
287 XCTAssertTrue(CFEqual(colorSpace, extendedSRGB));
288 CGColorSpaceRelease(extendedSRGB);
289 }
290
291 - (void)testOverlayViewStandardGamutKeepsBGRA8Unorm {
292 FlutterOverlayView* overlay =
293 [[FlutterOverlayView alloc] initWithContentsScale:2.0 pixelFormat:MTLPixelFormatBGRA8Unorm];
294 CAMetalLayer* layer = (CAMetalLayer*)overlay.layer;
295 XCTAssertEqual(layer.pixelFormat, MTLPixelFormatBGRA8Unorm);
296 }
297
298 - (void)testOverlayViewStandardGamutDoesNotSetExtendedColorSpace {
299 FlutterOverlayView* overlay =
300 [[FlutterOverlayView alloc] initWithContentsScale:2.0 pixelFormat:MTLPixelFormatBGRA8Unorm];
301 CAMetalLayer* layer = (CAMetalLayer*)overlay.layer;
302 XCTAssertNil((__bridge id)layer.colorspace);
303 }
304
305 - (void)testOverlayViewContentsScaleIsSet {
306 FlutterOverlayView* overlay =
307 [[FlutterOverlayView alloc] initWithContentsScale:3.0 pixelFormat:MTLPixelFormatBGRA10_XR];
308 XCTAssertEqual(overlay.layer.contentsScale, 3.0);
309 XCTAssertEqual(overlay.layer.rasterizationScale, 3.0);
310 }
311
312 @end
FlutterEngine.h
FlutterOverlayView.h
FlutterSceneDelegate.h
FlutterSceneLifeCycle_Internal.h
FlutterSceneLifeCycle_Test.h
FlutterView.h
FlutterView(Testing)
Definition: FlutterViewTest.mm:17
-[FlutterView(Testing) isWideGamutSupported]
BOOL isWideGamutSupported()
FakeDelegate
Definition: FlutterViewTest.mm:21
FakeDelegate::callbackCalled
BOOL callbackCalled
Definition: FlutterViewTest.mm:22
FlutterEngine
Definition: FlutterEngine.h:61
FlutterOverlayView
Definition: FlutterOverlayView.h:23
FlutterPluginSceneLifeCycleDelegate
Definition: FlutterSceneLifeCycle.h:149
FlutterPluginSceneLifeCycleDelegate::flutterManagedEngines
NSPointerArray * flutterManagedEngines
Definition: FlutterSceneLifeCycle_Test.h:14
FlutterSceneDelegate
Definition: FlutterSceneDelegate.h:21
FlutterView
Definition: FlutterView.h:50
-[FlutterView screen]
UIScreen * screen()
Definition: FlutterView.mm:42
-[FlutterView pixelFormat]
MTLPixelFormat pixelFormat()
Definition: FlutterView.mm:111
FlutterViewTest
Definition: FlutterViewTest.mm:46
FLUTTER_ASSERT_ARC
Definition: FlutterChannelKeyResponder.mm:13
FlutterViewEngineDelegate-p
Definition: FlutterView.h:14
FlutterViewEngineDelegate-p::platformViewsController
FlutterPlatformViewsController * platformViewsController
Definition: FlutterView.h:16
-[FlutterViewEngineDelegate-p flutterViewAccessibilityDidCall]
void flutterViewAccessibilityDidCall()