Flutter macOS Embedder
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>
8 
9 #include "third_party/googletest/googletest/include/gtest/gtest.h"
10 
12 
14 
15 @property(readonly, nonatomic, nonnull) NSMutableArray<NSView*>* pathClipViews;
16 @property(readonly, nonatomic, nullable) NSView* platformViewContainer;
17 
18 @end
19 
20 static constexpr float kMaxErr = 1e-10;
21 
22 namespace {
23 void 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.
40 void 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 
63 TEST(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  {
71  .type = kFlutterPlatformViewMutationTypeTransformation,
72  .transformation =
73  FlutterTransformation{
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 
90 TEST(FlutterMutatorViewTest, ClipsToBounds) {
91  NSView* platformView = [[NSView alloc] init];
92  FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
93  EXPECT_TRUE(mutatorView.clipsToBounds);
94 }
95 
96 TEST(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  {
106  .type = kFlutterPlatformViewMutationTypeTransformation,
107  .transformation =
108  FlutterTransformation{
109  .scaleX = 2,
110  .scaleY = 2,
111  },
112  },
113  {
114  .type = kFlutterPlatformViewMutationTypeTransformation,
115  .transformation =
116  FlutterTransformation{
117  .scaleX = 1,
118  .transX = 100,
119  .scaleY = 1,
120  .transY = 50,
121  },
122  },
123  {
124  .type = kFlutterPlatformViewMutationTypeTransformation,
125  .transformation =
126  FlutterTransformation{
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 
144 TEST(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  {
152  .type = kFlutterPlatformViewMutationTypeClipRect,
153  .clip_rect = FlutterRect{80, 40, 200, 100},
154  },
155  {
156  .type = kFlutterPlatformViewMutationTypeTransformation,
157  .transformation =
158  FlutterTransformation{
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 
173 TEST(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  {
181  .type = kFlutterPlatformViewMutationTypeClipRect,
182  .clip_rect = FlutterRect{80, 40, 200, 100},
183  },
184  {
185  .type = kFlutterPlatformViewMutationTypeClipRect,
186  .clip_rect = FlutterRect{110, 55, 120, 65},
187  },
188  {
189  .type = kFlutterPlatformViewMutationTypeTransformation,
190  .transformation =
191  FlutterTransformation{
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)));
203  EXPECT_TRUE(
204  CGRectEqualToRect(mutatorView.subviews.firstObject.frame, CGRectMake(-10, -5, 30, 20)));
205  EXPECT_TRUE(CGRectEqualToRect(platformView.frame, CGRectMake(0, 0, 30, 20)));
206 }
207 
208 TEST(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  {
218  .type = kFlutterPlatformViewMutationTypeTransformation,
219  .transformation =
220  FlutterTransformation{
221  .scaleX = 2,
222  .scaleY = 2,
223  },
224  },
225  {
226  .type = kFlutterPlatformViewMutationTypeClipRect,
227  .clip_rect = FlutterRect{80, 40, 200, 100},
228  },
229  {
230  .type = kFlutterPlatformViewMutationTypeClipRect,
231  .clip_rect = FlutterRect{110, 55, 120, 65},
232  },
233  {
234  .type = kFlutterPlatformViewMutationTypeTransformation,
235  .transformation =
236  FlutterTransformation{
237  .scaleX = 1,
238  .transX = 100,
239  .scaleY = 1,
240  .transY = 50,
241  },
242  },
243  {
244  .type = kFlutterPlatformViewMutationTypeTransformation,
245  .transformation =
246  FlutterTransformation{
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)));
258  EXPECT_TRUE(
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
264 TEST(FlutterMutatorViewTest, RoundRectClipsToSimpleRectangle) {
265  NSView* platformView = [[NSView alloc] init];
266  FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
267 
268  std::vector<FlutterPlatformViewMutation> mutations{
269  {
270  .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
271  .clip_rounded_rect =
272  FlutterRoundedRect{
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  {
281  .type = kFlutterPlatformViewMutationTypeTransformation,
282  .transformation =
283  FlutterTransformation{
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)));
295  EXPECT_TRUE(
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.
304 TEST(FlutterMutatorViewTest, ViewsSetIsFlipped) {
305  NSView* platformView = [[NSView alloc] init];
306  FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
307 
308  std::vector<FlutterPlatformViewMutation> mutations{
309  {
310  .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
311  .clip_rounded_rect =
312  FlutterRoundedRect{
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  {
321  .type = kFlutterPlatformViewMutationTypeTransformation,
322  .transformation =
323  FlutterTransformation{
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 
340 TEST(FlutterMutatorViewTest, RectsClipsToPathWhenRotated) {
341  NSView* platformView = [[NSView alloc] init];
342  FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
343  std::vector<FlutterPlatformViewMutation> mutations{
344  {
345  .type = kFlutterPlatformViewMutationTypeTransformation,
346  // Roation M_PI / 8
347  .transformation =
348  FlutterTransformation{
349  .scaleX = 0.9238795325112867,
350  .skewX = -0.3826834323650898,
351  .skewY = 0.3826834323650898,
352  .scaleY = 0.9238795325112867,
353  },
354  },
355  {
356  .type = kFlutterPlatformViewMutationTypeClipRect,
357  .clip_rect = FlutterRect{110, 60, 150, 150},
358  },
359  {
360  .type = kFlutterPlatformViewMutationTypeTransformation,
361  .transformation =
362  FlutterTransformation{
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 
376 TEST(FlutterMutatorViewTest, RoundRectClipsToPath) {
377  NSView* platformView = [[NSView alloc] init];
378  FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
379 
380  std::vector<FlutterPlatformViewMutation> mutations{
381  {
382  .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
383  .clip_rounded_rect =
384  FlutterRoundedRect{
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  {
393  .type = kFlutterPlatformViewMutationTypeTransformation,
394  .transformation =
395  FlutterTransformation{
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)));
407  EXPECT_TRUE(
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 
415 TEST(FlutterMutatorViewTest, PathClipViewsAreAddedAndRemoved) {
416  NSView* platformView = [[NSView alloc] init];
417  FlutterMutatorView* mutatorView = [[FlutterMutatorView alloc] initWithPlatformView:platformView];
418 
419  std::vector<FlutterPlatformViewMutation> mutations{
420  {
421  .type = kFlutterPlatformViewMutationTypeTransformation,
422  .transformation =
423  FlutterTransformation{
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  {
439  .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
440  .clip_rounded_rect =
441  FlutterRoundedRect{
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  {
450  .type = kFlutterPlatformViewMutationTypeTransformation,
451  .transformation =
452  FlutterTransformation{
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)));
464  EXPECT_TRUE(
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  {
475  .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
476  .clip_rounded_rect =
477  FlutterRoundedRect{
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  {
486  .type = kFlutterPlatformViewMutationTypeClipRoundedRect,
487  .clip_rounded_rect =
488  FlutterRoundedRect{
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  {
497  .type = kFlutterPlatformViewMutationTypeTransformation,
498  .transformation =
499  FlutterTransformation{
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)));
511  EXPECT_TRUE(
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)));
523  EXPECT_TRUE(
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 
538 TEST(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 
565 TEST(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 
730 TEST(FlutterMutatorViewTest, CursorCoordinator) {
731  [[[FlutterCursorCoordinatorTest alloc] init] testCoordinatorEventWithinFlutterContent];
732  [[[FlutterCursorCoordinatorTest alloc] init] testCoordinatorEventOutsideFlutterContent];
733 }
FlutterMutatorView.h
FlutterMutatorView
Definition: FlutterMutatorView.h:63
FlutterCursorCoordinator
Definition: FlutterMutatorView.h:45
-[FlutterMutatorView applyFlutterLayer:]
void applyFlutterLayer:(nonnull const flutter::PlatformViewLayer *layer)
FlutterMutatorView::platformView
NSView * platformView
Returns wrapped platform view.
Definition: FlutterMutatorView.h:72
FlutterMutatorView(Private)::pathClipViews
NSMutableArray< NSView * > * pathClipViews
Definition: FlutterMutatorViewTest.mm:15
kMaxErr
static constexpr float kMaxErr
Definition: FlutterMutatorViewTest.mm:20
-[FlutterMutatorView resetHitTestRegion]
void resetHitTestRegion()
Resets hit hit testing region for this mutator view.
Definition: FlutterMutatorView.mm:526
FlutterCursorCoordinator::cleanupScheduled
BOOL cleanupScheduled
Definition: FlutterMutatorView.h:54
FlutterMutatorView(Private)::platformViewContainer
NSView * platformViewContainer
Definition: FlutterMutatorViewTest.mm:16
FlutterCursorCoordinatorTest
Definition: FlutterMutatorViewTest.mm:587
-[FlutterMutatorView addHitTestIgnoreRegion:]
void addHitTestIgnoreRegion:(CGRect region)
Definition: FlutterMutatorView.mm:530
FlutterMutatorView(Private)
Definition: FlutterMutatorViewTest.mm:13
NSView+ClipsToBounds.h
-[FlutterCursorCoordinator processMouseMoveEvent:forMutatorView:overlayRegion:]
void processMouseMoveEvent:forMutatorView:overlayRegion:(nonnull NSEvent *event,[forMutatorView] nonnull FlutterMutatorView *view,[overlayRegion] const std::vector< CGRect > &region)
TEST
TEST(FlutterMutatorViewTest, BasicFrameIsCorrect)
Definition: FlutterMutatorViewTest.mm:63
FlutterView
Definition: FlutterView.h:35
flutter::PlatformViewLayer
Represents a platform view layer, including all mutations.
Definition: FlutterMutatorView.h:16
FlutterView.h