Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit cd17508

Browse files
committed
Add tests for cursor coordinator
1 parent ea2b223 commit cd17508

3 files changed

Lines changed: 171 additions & 5 deletions

File tree

shell/platform/darwin/macos/framework/Source/FlutterMutatorView.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66

77
#include "flutter/shell/platform/embedder/embedder.h"
88

9+
#include <vector>
10+
911
@class FlutterView;
12+
@class FlutterMutatorView;
1013

1114
/// FlutterCursorCoordinator is responsible for coordinating cursor changes between
1215
/// platform views and overlays of single FlutterView.
@@ -16,6 +19,16 @@
1619

1720
@end
1821

22+
/// Exposed methods for testing.
23+
@interface FlutterCursorCoordinator (Private)
24+
25+
@property(readonly, nonatomic) BOOL cleanupScheduled;
26+
27+
- (void)processMouseMoveEvent:(nonnull NSEvent*)event
28+
forMutatorView:(nonnull FlutterMutatorView*)view
29+
overlayRegion:(const std::vector<CGRect>&)region;
30+
@end
31+
1932
/// FlutterMutatorView contains platform view and is responsible for applying
2033
/// FlutterLayer mutations to it.
2134
@interface FlutterMutatorView : NSView

shell/platform/darwin/macos/framework/Source/FlutterMutatorView.mm

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88

99
#include <QuartzCore/QuartzCore.h>
1010

11-
#include <vector>
12-
1311
#include "flutter/fml/logging.h"
1412
#include "flutter/shell/platform/embedder/embedder.h"
1513

@@ -33,26 +31,31 @@ - (void)frameCleanup {
3331
NSCursor.flutterIgnoreCursorChange = NO;
3432
}
3533

34+
- (BOOL)cleanupScheduled {
35+
return _cleanupScheduled;
36+
}
37+
3638
// Processes the mouse event from given mutator view. This is called for each mutator view, in
3739
// z-order, from the top most down.
3840
- (void)processMouseMoveEvent:(NSEvent*)event
3941
forMutatorView:(FlutterMutatorView*)view
40-
overlayRegion:(std::vector<CGRect>&)region {
42+
overlayRegion:(const std::vector<CGRect>&)region {
4143
// [self frameCleanup] will be called once after current run loop turn.
4244
if (!_cleanupScheduled) {
4345
[[NSRunLoop mainRunLoop] performBlock:^{
4446
[self frameCleanup];
4547
}];
48+
_cleanupScheduled = YES;
4649
}
4750

48-
// Mouse move was alrady handled by a mutator view above.
51+
// Mouse move was already handled by a mutator view above.
4952
if (_mouseMoveHandled) {
5053
return;
5154
}
5255

5356
NSPoint point = [view convertPoint:event.locationInWindow fromView:nil];
5457

55-
// If the mouse is above overaly region restore current Flutter cursor.
58+
// If the mouse is above overlay region restore current Flutter cursor.
5659
for (const auto& r : region) {
5760
if (CGRectContainsPoint(r, point)) {
5861
[_flutterView cursorUpdate:event];

shell/platform/darwin/macos/framework/Source/FlutterMutatorViewTest.mm

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
#import <OCMock/OCMock.h>
56
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterMutatorView.h"
7+
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h"
68

79
#include "third_party/googletest/googletest/include/gtest/gtest.h"
810

@@ -563,4 +565,152 @@ void ExpectTransform3DEqual(const CATransform3D& t, const CATransform3D& u) {
563565
EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 50)], platformView);
564566
EXPECT_EQ([mutatorView hitTest:NSMakePoint(50, 10)], platformView);
565567
EXPECT_EQ([mutatorView hitTest:NSMakePoint(10, 50)], platformView);
568+
}
569+
570+
@interface FlutterCursorCoordinatorTest : NSObject
571+
572+
@end
573+
574+
@implementation FlutterCursorCoordinatorTest
575+
- (void)testCoordinatorEventWithinFlutterContent {
576+
id flutterView = OCMClassMock([FlutterView class]);
577+
FlutterCursorCoordinator* coordinator =
578+
[[FlutterCursorCoordinator alloc] initWithFlutterView:flutterView];
579+
{
580+
id platformView = OCMClassMock([NSView class]);
581+
OCMStub([flutterView cursorUpdate:[OCMArg any]]);
582+
id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
583+
OCMStub([mutatorView platformView]).andReturn(platformView);
584+
CGPoint location = NSMakePoint(50, 50);
585+
OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
586+
NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
587+
location:location
588+
modifierFlags:0
589+
timestamp:0
590+
windowNumber:0
591+
context:nil
592+
eventNumber:0
593+
clickCount:0
594+
pressure:0];
595+
[coordinator processMouseMoveEvent:event
596+
forMutatorView:mutatorView
597+
overlayRegion:{CGRectMake(0, 0, 100, 100)}];
598+
OCMVerify([flutterView cursorUpdate:event]);
599+
}
600+
{
601+
id platformView = OCMClassMock([NSView class]);
602+
// Make sure once event is handled the coordinator will not send cursorUpdate again.
603+
OCMReject([flutterView cursorUpdate:[OCMArg any]]);
604+
id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
605+
OCMStub([mutatorView platformView]).andReturn(platformView);
606+
CGPoint location = NSMakePoint(50, 50);
607+
OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
608+
NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
609+
location:location
610+
modifierFlags:0
611+
timestamp:0
612+
windowNumber:0
613+
context:nil
614+
eventNumber:0
615+
clickCount:0
616+
pressure:0];
617+
[coordinator processMouseMoveEvent:event
618+
forMutatorView:mutatorView
619+
overlayRegion:{CGRectMake(0, 0, 100, 100)}];
620+
}
621+
EXPECT_TRUE(coordinator.cleanupScheduled);
622+
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
623+
EXPECT_FALSE(coordinator.cleanupScheduled);
624+
}
625+
626+
- (void)testCoordinatorEventOutsideFlutterContent {
627+
id flutterView = OCMClassMock([FlutterView class]);
628+
OCMReject([flutterView cursorUpdate:[OCMArg any]]);
629+
FlutterCursorCoordinator* coordinator =
630+
[[FlutterCursorCoordinator alloc] initWithFlutterView:flutterView];
631+
id platformViewWindow = OCMClassMock([NSWindow class]);
632+
{
633+
id platformView = OCMClassMock([NSView class]);
634+
OCMStub([platformViewWindow invalidateCursorRectsForView:platformView]);
635+
OCMStub([platformView window]).andReturn(platformViewWindow);
636+
OCMStub([flutterView cursorUpdate:[OCMArg any]]);
637+
id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
638+
OCMStub([mutatorView platformView]).andReturn(platformView);
639+
CGPoint location = NSMakePoint(150, 150);
640+
OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
641+
NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
642+
location:location
643+
modifierFlags:0
644+
timestamp:0
645+
windowNumber:0
646+
context:nil
647+
eventNumber:0
648+
clickCount:0
649+
pressure:0];
650+
[coordinator processMouseMoveEvent:event
651+
forMutatorView:mutatorView
652+
overlayRegion:{CGRectMake(0, 0, 100, 100)}];
653+
OCMVerify([platformViewWindow invalidateCursorRectsForView:platformView]);
654+
}
655+
{
656+
// Make sure this is not called again for subsequent invocation during same run loop turn.
657+
OCMReject([platformViewWindow invalidateCursorRectsForView:[OCMArg any]]);
658+
659+
id platformView = OCMClassMock([NSView class]);
660+
OCMStub([platformViewWindow invalidateCursorRectsForView:platformView]);
661+
OCMStub([platformView window]).andReturn(platformViewWindow);
662+
OCMStub([flutterView cursorUpdate:[OCMArg any]]);
663+
id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
664+
OCMStub([mutatorView platformView]).andReturn(platformView);
665+
CGPoint location = NSMakePoint(150, 150);
666+
OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
667+
NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
668+
location:location
669+
modifierFlags:0
670+
timestamp:0
671+
windowNumber:0
672+
context:nil
673+
eventNumber:0
674+
clickCount:0
675+
pressure:0];
676+
[coordinator processMouseMoveEvent:event
677+
forMutatorView:mutatorView
678+
overlayRegion:{CGRectMake(0, 0, 100, 100)}];
679+
}
680+
EXPECT_TRUE(coordinator.cleanupScheduled);
681+
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
682+
EXPECT_FALSE(coordinator.cleanupScheduled);
683+
684+
// Check that invalidateCursorRectsForView is called again
685+
platformViewWindow = OCMClassMock([NSWindow class]);
686+
{
687+
id platformView = OCMClassMock([NSView class]);
688+
OCMStub([platformViewWindow invalidateCursorRectsForView:platformView]);
689+
OCMStub([platformView window]).andReturn(platformViewWindow);
690+
OCMStub([flutterView cursorUpdate:[OCMArg any]]);
691+
id mutatorView = OCMStrictClassMock([FlutterMutatorView class]);
692+
OCMStub([mutatorView platformView]).andReturn(platformView);
693+
CGPoint location = NSMakePoint(150, 150);
694+
OCMStub([mutatorView convertPoint:location fromView:[OCMArg any]]).andReturn(location);
695+
NSEvent* event = [NSEvent mouseEventWithType:NSEventTypeMouseMoved
696+
location:location
697+
modifierFlags:0
698+
timestamp:0
699+
windowNumber:0
700+
context:nil
701+
eventNumber:0
702+
clickCount:0
703+
pressure:0];
704+
[coordinator processMouseMoveEvent:event
705+
forMutatorView:mutatorView
706+
overlayRegion:{CGRectMake(0, 0, 100, 100)}];
707+
OCMVerify([platformViewWindow invalidateCursorRectsForView:platformView]);
708+
}
709+
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
710+
}
711+
@end
712+
713+
TEST(FlutterMutatorViewTest, CursorCoordinator) {
714+
[[[FlutterCursorCoordinatorTest alloc] init] testCoordinatorEventWithinFlutterContent];
715+
[[[FlutterCursorCoordinatorTest alloc] init] testCoordinatorEventOutsideFlutterContent];
566716
}

0 commit comments

Comments
 (0)