Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
FlutterMutatorViewTest.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 "flutter/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.h"
7#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
8
9#include "third_party/googletest/googletest/include/gtest/gtest.h"
10
11#import "flutter/shell/platform/darwin/macos/framework/Source/NSView+ClipsToBounds.h"
12
13@interface FlutterMutatorView (Private)
14
15@property(readonly, nonatomic, nonnull) NSMutableArray<NSView*>* pathClipViews;
16@property(readonly, nonatomic, nullable) NSView* platformViewContainer;
17
18@end
19
20static constexpr float kMaxErr = 1e-10;
21
22namespace {
23void ApplyFlutterLayer(FlutterMutatorView* view,
24 FlutterSize size,
25 const std::vector<FlutterPlatformViewMutation>& mutations) {
26 flutter::PlatformViewLayer layer(0, // identifier
27 mutations,
28 // Offset is ignored by mutator view, the bounding rect is
29 // determined by width and transform.
30 FlutterPoint{0, 0}, // offset
31 size);
32
33 [view applyFlutterLayer:&layer];
34}
35
36// Expect that each element within two CATransform3Ds is within an error bound.
37//
38// In order to avoid architecture-specific floating point differences we don't check for exact
39// equality using, for example, CATransform3DEqualToTransform.
40void ExpectTransform3DEqual(const CATransform3D& t, const CATransform3D& u) {
41 EXPECT_NEAR(t.m11, u.m11, kMaxErr);
42 EXPECT_NEAR(t.m12, u.m12, kMaxErr);
43 EXPECT_NEAR(t.m13, u.m13, kMaxErr);
44 EXPECT_NEAR(t.m14, u.m14, kMaxErr);
45
46 EXPECT_NEAR(t.m21, u.m21, kMaxErr);
47 EXPECT_NEAR(t.m22, u.m22, kMaxErr);
48 EXPECT_NEAR(t.m23, u.m23, kMaxErr);
49 EXPECT_NEAR(t.m24, u.m24, kMaxErr);
50
51 EXPECT_NEAR(t.m31, u.m31, kMaxErr);
52 EXPECT_NEAR(t.m32, u.m32, kMaxErr);
53 EXPECT_NEAR(t.m33, u.m33, kMaxErr);
54 EXPECT_NEAR(t.m34, u.m34, kMaxErr);
55
56 EXPECT_NEAR(t.m41, u.m41, kMaxErr);
57 EXPECT_NEAR(t.m42, u.m42, kMaxErr);
58 EXPECT_NEAR(t.m43, u.m43, kMaxErr);
59 EXPECT_NEAR(t.m44, u.m44, kMaxErr);
60}
61} // namespace
62
63TEST(FlutterMutatorViewTest, BasicFrameIsCorrect) {
64 NSView* platformView = [[NSView alloc] init];
65 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
66
67 EXPECT_EQ(mutatorView.platformView, platformView);
68
69 std::vector<FlutterPlatformViewMutation> mutations{
70 {
72 .transformation =
74 .scaleX = 1,
75 .transX = 100,
76 .scaleY = 1,
77 .transY = 50,
78 },
79 },
80 };
81
82 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
83
84 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(100, 50, 30, 20)));
85 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
86 EXPECT_EQ(mutatorView.pathClipViews.count, 0ull);
87 EXPECT_NE(mutatorView.platformViewContainer, nil);
88}
89
90TEST(FlutterMutatorViewTest, ClipsToBounds) {
91 NSView* platformView = [[NSView alloc] init];
92 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
93 EXPECT_TRUE(mutatorView.clipsToBounds);
94}
95
96TEST(FlutterMutatorViewTest, TransformedFrameIsCorrect) {
97 NSView* platformView = [[NSView alloc] init];
98 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
99 NSView* mutatorViewParent = [[NSView alloc] init];
100 mutatorViewParent.wantsLayer = YES;
101 mutatorViewParent.layer.contentsScale = 2.0;
102 [mutatorViewParent addSubview:mutatorView];
103
104 std::vector<FlutterPlatformViewMutation> mutations{
105 {
107 .transformation =
109 .scaleX = 2,
110 .scaleY = 2,
111 },
112 },
113 {
115 .transformation =
117 .scaleX = 1,
118 .transX = 100,
119 .scaleY = 1,
120 .transY = 50,
121 },
122 },
123 {
125 .transformation =
127 .scaleX = 1.5,
128 .transX = -7.5,
129 .scaleY = 1.5,
130 .transY = -5,
131 },
132 },
133 };
134
135 // PlatformView size form engine comes in physical pixels
136 ApplyFlutterLayer(mutatorView, FlutterSize{30 * 2, 20 * 2}, mutations);
137 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(92.5, 45, 45, 30)));
138 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
139
140 ExpectTransform3DEqual(mutatorView.platformViewContainer.layer.sublayerTransform,
141 CATransform3DMakeScale(1.5, 1.5, 1));
142}
143
144TEST(FlutterMutatorViewTest, FrameWithLooseClipIsCorrect) {
145 NSView* platformView = [[NSView alloc] init];
146 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
147
148 EXPECT_EQ(mutatorView.platformView, platformView);
149
150 std::vector<FlutterPlatformViewMutation> mutations{
151 {
153 .clip_rect = FlutterRect{80, 40, 200, 100},
154 },
155 {
157 .transformation =
159 .scaleX = 1,
160 .transX = 100,
161 .scaleY = 1,
162 .transY = 50,
163 },
164 },
165 };
166
167 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
168
169 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(100, 50, 30, 20)));
170 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
171}
172
173TEST(FlutterMutatorViewTest, FrameWithTightClipIsCorrect) {
174 NSView* platformView = [[NSView alloc] init];
175 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
176
177 EXPECT_EQ(mutatorView.platformView, platformView);
178
179 std::vector<FlutterPlatformViewMutation> mutations{
180 {
182 .clip_rect = FlutterRect{80, 40, 200, 100},
183 },
184 {
186 .clip_rect = FlutterRect{110, 55, 120, 65},
187 },
188 {
190 .transformation =
192 .scaleX = 1,
193 .transX = 100,
194 .scaleY = 1,
195 .transY = 50,
196 },
197 },
198 };
199
200 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
201
202 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 55, 10, 10)));
204 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -5, 30, 20)));
205 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
206}
207
208TEST(FlutterMutatorViewTest, FrameWithTightClipAndTransformIsCorrect) {
209 NSView* platformView = [[NSView alloc] init];
210 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
211 NSView* mutatorViewParent = [[NSView alloc] init];
212 mutatorViewParent.wantsLayer = YES;
213 mutatorViewParent.layer.contentsScale = 2.0;
214 [mutatorViewParent addSubview:mutatorView];
215
216 std::vector<FlutterPlatformViewMutation> mutations{
217 {
219 .transformation =
221 .scaleX = 2,
222 .scaleY = 2,
223 },
224 },
225 {
227 .clip_rect = FlutterRect{80, 40, 200, 100},
228 },
229 {
231 .clip_rect = FlutterRect{110, 55, 120, 65},
232 },
233 {
235 .transformation =
237 .scaleX = 1,
238 .transX = 100,
239 .scaleY = 1,
240 .transY = 50,
241 },
242 },
243 {
245 .transformation =
247 .scaleX = 1.5,
248 .transX = -7.5,
249 .scaleY = 1.5,
250 .transY = -5,
251 },
252 },
253 };
254
255 ApplyFlutterLayer(mutatorView, FlutterSize{30 * 2, 20 * 2}, mutations);
256
257 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 55, 10, 10)));
259 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-17.5, -10, 45, 30)));
260 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
261}
262
263// Rounded rectangle without hitting the corner
264TEST(FlutterMutatorViewTest, RoundRectClipsToSimpleRectangle) {
265 NSView* platformView = [[NSView alloc] init];
266 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
267
268 std::vector<FlutterPlatformViewMutation> mutations{
269 {
271 .clip_rounded_rect =
273 .rect = FlutterRect{110, 30, 120, 90},
274 .upper_left_corner_radius = FlutterSize{10, 10},
275 .upper_right_corner_radius = FlutterSize{10, 10},
276 .lower_right_corner_radius = FlutterSize{10, 10},
277 .lower_left_corner_radius = FlutterSize{10, 10},
278 },
279 },
280 {
282 .transformation =
284 .scaleX = 1,
285 .transX = 100,
286 .scaleY = 1,
287 .transY = 50,
288 },
289 },
290 };
291
292 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
293
294 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 50, 10, 20)));
296 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, 0, 30, 20)));
297 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
298 EXPECT_EQ(mutatorView.pathClipViews.count, 0ul);
299}
300
301// Ensure that the mutator view, clip views, and container all use a flipped y axis. The transforms
302// sent from the framework assume this, and so aside from the consistency with every other embedder,
303// we can avoid a lot of extra math.
304TEST(FlutterMutatorViewTest, ViewsSetIsFlipped) {
305 NSView* platformView = [[NSView alloc] init];
306 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
307
308 std::vector<FlutterPlatformViewMutation> mutations{
309 {
311 .clip_rounded_rect =
313 .rect = FlutterRect{110, 60, 150, 150},
314 .upper_left_corner_radius = FlutterSize{10, 10},
315 .upper_right_corner_radius = FlutterSize{10, 10},
316 .lower_right_corner_radius = FlutterSize{10, 10},
317 .lower_left_corner_radius = FlutterSize{10, 10},
318 },
319 },
320 {
322 .transformation =
324 .scaleX = 1,
325 .transX = 100,
326 .scaleY = 1,
327 .transY = 50,
328 },
329 },
330 };
331
332 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
333
334 EXPECT_TRUE(mutatorView.isFlipped);
335 ASSERT_EQ(mutatorView.pathClipViews.count, 1ul);
336 EXPECT_TRUE(mutatorView.pathClipViews.firstObject.isFlipped);
337 EXPECT_TRUE(mutatorView.platformViewContainer.isFlipped);
338}
339
340TEST(FlutterMutatorViewTest, RectsClipsToPathWhenRotated) {
341 NSView* platformView = [[NSView alloc] init];
342 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
343 std::vector<FlutterPlatformViewMutation> mutations{
344 {
346 // Roation M_PI / 8
347 .transformation =
349 .scaleX = 0.9238795325112867,
350 .skewX = -0.3826834323650898,
351 .skewY = 0.3826834323650898,
352 .scaleY = 0.9238795325112867,
353 },
354 },
355 {
357 .clip_rect = FlutterRect{110, 60, 150, 150},
358 },
359 {
361 .transformation =
363 .scaleX = 1,
364 .transX = 100,
365 .scaleY = 1,
366 .transY = 50,
367 },
368 },
369 };
370 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
371 EXPECT_EQ(mutatorView.pathClipViews.count, 1ul);
372 EXPECT_NEAR(mutatorView.platformViewContainer.frame.size.width, 35.370054622640396, kMaxErr);
373 EXPECT_NEAR(mutatorView.platformViewContainer.frame.size.height, 29.958093621178421, kMaxErr);
374}
375
376TEST(FlutterMutatorViewTest, RoundRectClipsToPath) {
377 NSView* platformView = [[NSView alloc] init];
378 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
379
380 std::vector<FlutterPlatformViewMutation> mutations{
381 {
383 .clip_rounded_rect =
385 .rect = FlutterRect{110, 60, 150, 150},
386 .upper_left_corner_radius = FlutterSize{10, 10},
387 .upper_right_corner_radius = FlutterSize{10, 10},
388 .lower_right_corner_radius = FlutterSize{10, 10},
389 .lower_left_corner_radius = FlutterSize{10, 10},
390 },
391 },
392 {
394 .transformation =
396 .scaleX = 1,
397 .transX = 100,
398 .scaleY = 1,
399 .transY = 50,
400 },
401 },
402 };
403
404 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
405
406 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 60, 20, 10)));
408 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -10, 30, 20)));
409 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
410 EXPECT_EQ(mutatorView.pathClipViews.count, 1ul);
411 ExpectTransform3DEqual(mutatorView.pathClipViews.firstObject.layer.mask.transform,
412 CATransform3DMakeTranslation(-100, -50, 0));
413}
414
415TEST(FlutterMutatorViewTest, PathClipViewsAreAddedAndRemoved) {
416 NSView* platformView = [[NSView alloc] init];
417 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
418
419 std::vector<FlutterPlatformViewMutation> mutations{
420 {
422 .transformation =
424 .scaleX = 1,
425 .transX = 100,
426 .scaleY = 1,
427 .transY = 50,
428 },
429 },
430 };
431
432 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
433
434 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(100, 50, 30, 20)));
435 EXPECT_EQ(mutatorView.pathClipViews.count, 0ull);
436
437 std::vector<FlutterPlatformViewMutation> mutations2{
438 {
440 .clip_rounded_rect =
442 .rect = FlutterRect{110, 60, 150, 150},
443 .upper_left_corner_radius = FlutterSize{10, 10},
444 .upper_right_corner_radius = FlutterSize{10, 10},
445 .lower_right_corner_radius = FlutterSize{10, 10},
446 .lower_left_corner_radius = FlutterSize{10, 10},
447 },
448 },
449 {
451 .transformation =
453 .scaleX = 1,
454 .transX = 100,
455 .scaleY = 1,
456 .transY = 50,
457 },
458 },
459 };
460
461 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations2);
462
463 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 60, 20, 10)));
465 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -10, 30, 20)));
466 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
467 EXPECT_EQ(mutatorView.pathClipViews.count, 1ul);
468
469 EXPECT_EQ(platformView.superview, mutatorView.platformViewContainer);
470 EXPECT_EQ(mutatorView.platformViewContainer.superview, mutatorView.pathClipViews[0]);
471 EXPECT_EQ(mutatorView.pathClipViews[0].superview, mutatorView);
472
473 std::vector<FlutterPlatformViewMutation> mutations3{
474 {
476 .clip_rounded_rect =
478 .rect = FlutterRect{110, 55, 150, 150},
479 .upper_left_corner_radius = FlutterSize{10, 10},
480 .upper_right_corner_radius = FlutterSize{10, 10},
481 .lower_right_corner_radius = FlutterSize{10, 10},
482 .lower_left_corner_radius = FlutterSize{10, 10},
483 },
484 },
485 {
487 .clip_rounded_rect =
489 .rect = FlutterRect{30, 30, 120, 65},
490 .upper_left_corner_radius = FlutterSize{10, 10},
491 .upper_right_corner_radius = FlutterSize{10, 10},
492 .lower_right_corner_radius = FlutterSize{10, 10},
493 .lower_left_corner_radius = FlutterSize{10, 10},
494 },
495 },
496 {
498 .transformation =
500 .scaleX = 1,
501 .transX = 100,
502 .scaleY = 1,
503 .transY = 50,
504 },
505 },
506 };
507
508 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations3);
509
510 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 55, 10, 10)));
512 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -5, 30, 20)));
513 EXPECT_EQ(mutatorView.pathClipViews.count, 2ul);
514
515 EXPECT_EQ(platformView.superview, mutatorView.platformViewContainer);
516 EXPECT_EQ(mutatorView.platformViewContainer.superview, mutatorView.pathClipViews[1]);
517 EXPECT_EQ(mutatorView.pathClipViews[1].superview, mutatorView.pathClipViews[0]);
518 EXPECT_EQ(mutatorView.pathClipViews[0].superview, mutatorView);
519
520 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations2);
521
522 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(110, 60, 20, 10)));
524 CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -10, 30, 20)));
525 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
526 EXPECT_EQ(mutatorView.pathClipViews.count, 1ul);
527
528 EXPECT_EQ(platformView.superview, mutatorView.platformViewContainer);
529 EXPECT_EQ(mutatorView.platformViewContainer.superview, mutatorView.pathClipViews[0]);
530 EXPECT_EQ(mutatorView.pathClipViews[0].superview, mutatorView);
531
532 ApplyFlutterLayer(mutatorView, FlutterSize{30, 20}, mutations);
533
534 EXPECT_TRUE(CGRectEqualToRect(mutatorView.frame, CGRectMake(100, 50, 30, 20)));
535 EXPECT_EQ(mutatorView.pathClipViews.count, 0ull);
536}
537
538TEST(FlutterMutatorViewTest, HitTestIgnoreRegion) {
539 NSView* platformView = [[NSView alloc] init];
540 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
541 ApplyFlutterLayer(mutatorView, FlutterSize{100, 100}, {});
542 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 10)], platformView);
543 EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 10)], platformView);
544
545 [mutatorView resetHitTestRegion];
546 [mutatorView addHitTestIgnoreRegion:CGRectMake(0, 0, 50, 50)];
547 [mutatorView addHitTestIgnoreRegion:CGRectMake(50, 50, 50, 50)];
548
549 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 10)], nil);
550 EXPECT_EQ([mutatorView hitTest:NSMakePoint(49, 10)], nil);
551 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 49)], nil);
552 EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 50)], nil);
553 EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 10)], platformView);
554 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 50)], platformView);
555
556 [mutatorView resetHitTestRegion];
557 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 10)], platformView);
558 EXPECT_EQ([mutatorView hitTest:NSMakePoint(49, 10)], platformView);
559 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 49)], platformView);
560 EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 50)], platformView);
561 EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 10)], platformView);
562 EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 50)], platformView);
563}
564
565TEST(FlutterMutatorViewTest, ReparentingPlatformView) {
566 NSView* platformView = [[NSView alloc] init];
567 FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
568 ApplyFlutterLayer(mutatorView, FlutterSize{100, 100}, {});
569 EXPECT_TRUE(platformView.superview == mutatorView.platformViewContainer);
570 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 100, 100)));
571
572 // Reparent platform view and replace it with placeholder (mimicking WKWebKit going full screen)
573 NSView* newParent = [[NSView alloc] init];
574 [newParent addSubview:platformView];
575 platformView.frame = CGRectMake(10, 10, 200, 200);
576
577 NSView* placeholderView = [[NSView alloc] init];
578 [mutatorView.platformViewContainer addSubview:placeholderView];
579 ApplyFlutterLayer(mutatorView, FlutterSize{100, 100}, {});
580
581 // Platform view should not be touched but the replacement view should be properly positioned.
582 EXPECT_TRUE(platformView.superview == newParent);
583 EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(10, 10, 200, 200)));
584 EXPECT_TRUE(CGRectEqualToRect(placeholderView.frame, CGRectMake(0, 0, 100, 100)));
585}
586
587@interface FlutterCursorCoordinatorTest : NSObject
588
589@end
590
591@implementation FlutterCursorCoordinatorTest
592- (void)testCoordinatorEventWithinFlutterContent {
593 id flutterView = OCMClassMock([FlutterView class]);
594 FlutterCursorCoordinator* coordinator =
595 [[FlutterCursorCoordinator alloc] initWithFlutterView:flutterView];
596 {
597 id platformView = OCMClassMock([NSView class]);
598 OCMStub([flutterView cursorUpdate:[OCMArg any]]);
599 id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
600 OCMStub([mutatorView platformView]).andReturn(platformView);
601 CGPoint location = NSMakePoint(50, 50);
602 OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
603 NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
604 location:location
605 modifierFlags:0
606 timestamp:0
607 windowNumber:0
608 context:nil
609 eventNumber:0
610 clickCount:0
611 pressure:0];
612 [coordinator processMouseMoveEvent:event
613 forMutatorView:mutatorView
614 overlayRegion:{CGRectMake(0, 0, 100, 100)}];
615 OCMVerify([flutterView cursorUpdate:event]);
616 }
617 {
618 id platformView = OCMClassMock([NSView class]);
619 // Make sure once event is handled the coordinator will not send cursorUpdate again.
620 OCMReject([flutterView cursorUpdate:[OCMArg any]]);
621 id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
622 OCMStub([mutatorView platformView]).andReturn(platformView);
623 CGPoint location = NSMakePoint(50, 50);
624 OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
625 NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
626 location:location
627 modifierFlags:0
628 timestamp:0
629 windowNumber:0
630 context:nil
631 eventNumber:0
632 clickCount:0
633 pressure:0];
634 [coordinator processMouseMoveEvent:event
635 forMutatorView:mutatorView
636 overlayRegion:{CGRectMake(0, 0, 100, 100)}];
637 }
638 EXPECT_TRUE(coordinator.cleanupScheduled);
639 [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
640 EXPECT_FALSE(coordinator.cleanupScheduled);
641}
642
643- (void)testCoordinatorEventOutsideFlutterContent {
644 id flutterView = OCMClassMock([FlutterView class]);
645 OCMReject([flutterView cursorUpdate:[OCMArg any]]);
646 FlutterCursorCoordinator* coordinator =
647 [[FlutterCursorCoordinator alloc] initWithFlutterView:flutterView];
648 id platformViewWindow = OCMClassMock([NSWindow class]);
649 {
650 id platformView = OCMClassMock([NSView class]);
651 OCMStub([platformViewWindow invalidateCursorRectsForView:platformView]);
652 OCMStub([platformView window]).andReturn(platformViewWindow);
653 OCMStub([flutterView cursorUpdate:[OCMArg any]]);
654 id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
655 OCMStub([mutatorView platformView]).andReturn(platformView);
656 CGPoint location = NSMakePoint(150, 150);
657 OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
658 NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
659 location:location
660 modifierFlags:0
661 timestamp:0
662 windowNumber:0
663 context:nil
664 eventNumber:0
665 clickCount:0
666 pressure:0];
667 [coordinator processMouseMoveEvent:event
668 forMutatorView:mutatorView
669 overlayRegion:{CGRectMake(0, 0, 100, 100)}];
670 OCMVerify([platformViewWindow invalidateCursorRectsForView:platformView]);
671 }
672 {
673 // Make sure this is not called again for subsequent invocation during same run loop turn.
674 OCMReject([platformViewWindow invalidateCursorRectsForView:[OCMArg any]]);
675
676 id platformView = OCMClassMock([NSView class]);
677 OCMStub([platformViewWindow invalidateCursorRectsForView:platformView]);
678 OCMStub([platformView window]).andReturn(platformViewWindow);
679 OCMStub([flutterView cursorUpdate:[OCMArg any]]);
680 id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
681 OCMStub([mutatorView platformView]).andReturn(platformView);
682 CGPoint location = NSMakePoint(150, 150);
683 OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
684 NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
685 location:location
686 modifierFlags:0
687 timestamp:0
688 windowNumber:0
689 context:nil
690 eventNumber:0
691 clickCount:0
692 pressure:0];
693 [coordinator processMouseMoveEvent:event
694 forMutatorView:mutatorView
695 overlayRegion:{CGRectMake(0, 0, 100, 100)}];
696 }
697 EXPECT_TRUE(coordinator.cleanupScheduled);
698 [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
699 EXPECT_FALSE(coordinator.cleanupScheduled);
700
701 // Check that invalidateCursorRectsForView is called again
702 platformViewWindow = OCMClassMock([NSWindow class]);
703 {
704 id platformView = OCMClassMock([NSView class]);
705 OCMStub([platformViewWindow invalidateCursorRectsForView:platformView]);
706 OCMStub([platformView window]).andReturn(platformViewWindow);
707 OCMStub([flutterView cursorUpdate:[OCMArg any]]);
708 id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
709 OCMStub([mutatorView platformView]).andReturn(platformView);
710 CGPoint location = NSMakePoint(150, 150);
711 OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
712 NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
713 location:location
714 modifierFlags:0
715 timestamp:0
716 windowNumber:0
717 context:nil
718 eventNumber:0
719 clickCount:0
720 pressure:0];
721 [coordinator processMouseMoveEvent:event
722 forMutatorView:mutatorView
723 overlayRegion:{CGRectMake(0, 0, 100, 100)}];
724 OCMVerify([platformViewWindow invalidateCursorRectsForView:platformView]);
725 }
726 [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
727}
728@end
729
730TEST(FlutterMutatorViewTest, CursorCoordinator) {
731 [[[FlutterCursorCoordinatorTest alloc] init] testCoordinatorEventWithinFlutterContent];
732 [[[FlutterCursorCoordinatorTest alloc] init] testCoordinatorEventOutsideFlutterContent];
733}
#define TEST(S, s, D, expected)
static constexpr float kMaxErr
NSMutableArray< NSView * > * pathClipViews
Represents a platform view layer, including all mutations.
@ kFlutterPlatformViewMutationTypeClipRoundedRect
Definition embedder.h:1695
@ kFlutterPlatformViewMutationTypeClipRect
Definition embedder.h:1692
@ kFlutterPlatformViewMutationTypeTransformation
Definition embedder.h:1698
GLFWwindow * window
Definition main.cc:45
FlKeyEvent * event
void processMouseMoveEvent:forMutatorView:overlayRegion:(NSEvent *event, [forMutatorView] FlutterMutatorView *view, [overlayRegion] const std::vector< CGRect > &region)
NSMutableArray * pathClipViews()
NSView * platformView
Returns wrapped platform view.
void resetHitTestRegion()
Resets hit hit testing region for this mutator view.
void applyFlutterLayer:(nonnull const flutter::PlatformViewLayer *layer)
CGFloat contentsScale()
Returns the scale factor to translate logical pixels to physical pixels for this view.
void addHitTestIgnoreRegion:(CGRect region)
A structure to represent a 2D point.
Definition embedder.h:443
A structure to represent a rectangle.
Definition embedder.h:435
A structure to represent a rounded rectangle.
Definition embedder.h:449
FlutterRect rect
Definition embedder.h:450
A structure to represent the width and height.
Definition embedder.h:421
double scaleX
horizontal scale factor
Definition embedder.h:277
#define EXPECT_TRUE(handle)
Definition unit_test.h:685