@@ -75,13 +75,16 @@ - (void)testPointerButtons {
7575 XCTAssertTrue (
7676 [app.textFields[@" 2,PointerChange.up,device=0,buttons=0" ] waitForExistenceWithTimeout: 1 ],
7777 @" PointerChange.up event did not occur for a normal tap" );
78+ XCTAssertTrue (
79+ [app.textFields[@" 3,PointerChange.remove,device=0,buttons=0" ] waitForExistenceWithTimeout: 1 ],
80+ @" PointerChange.remove event did not occur for a normal tap" );
7881 SEL rightClick = @selector (rightClick );
7982 XCTAssertTrue ([flutterView respondsToSelector: rightClick],
8083 @" If supportsPointerInteraction is true, this should be true too." );
8184 [flutterView performSelector: rightClick];
8285 // On simulated right click, a hover also occurs, so the hover pointer is added
8386 XCTAssertTrue (
84- [app.textFields[@" 3 ,PointerChange.add,device=1,buttons=0" ] waitForExistenceWithTimeout: 1 ],
87+ [app.textFields[@" 4 ,PointerChange.add,device=1,buttons=0" ] waitForExistenceWithTimeout: 1 ],
8588 @" PointerChange.add event did not occur for a right-click's hover pointer" );
8689
8790 // The hover pointer is removed after ~3.5 seconds, this ensures that all events are received
@@ -137,7 +140,7 @@ - (void)testPointerButtons {
137140 @" Right-click pointer was pressed before it was added" );
138141 XCTAssertGreaterThan (rightClickUpSequenceNumber, rightClickDownSequenceNumber,
139142 @" Right-click pointer was released before it was pressed" );
140- XCTAssertGreaterThan ([[hoverSequenceNumbers firstObject ] intValue ], 3 ,
143+ XCTAssertGreaterThan ([[hoverSequenceNumbers firstObject ] intValue ], 4 ,
141144 @" Hover occured before hover pointer was added" );
142145 XCTAssertGreaterThan (hoverRemovedSequenceNumber, [[hoverSequenceNumbers lastObject ] intValue ],
143146 @" Hover occured after hover pointer was removed" );
@@ -196,6 +199,99 @@ - (void)testPointerHover {
196199 XCTAssertTrue ([app.textFields[removeMessage] waitForExistenceWithTimeout: 1 ],
197200 @" PointerChange.remove event did not occur for a hover" );
198201}
202+
203+ - (void )testPointerScroll {
204+ BOOL supportsPointerInteraction = NO ;
205+ SEL supportsPointerInteractionSelector = @selector (supportsPointerInteraction );
206+ if ([XCUIDevice.sharedDevice respondsToSelector: supportsPointerInteractionSelector]) {
207+ supportsPointerInteraction =
208+ performBoolSelector (XCUIDevice.sharedDevice , supportsPointerInteractionSelector);
209+ }
210+ XCTSkipUnless (supportsPointerInteraction, " Device does not support pointer interaction." );
211+ XCUIApplication* app = [[XCUIApplication alloc ] init ];
212+ app.launchArguments = @[ @" --pointer-events" ];
213+ [app launch ];
214+
215+ NSPredicate * predicateToFindFlutterView =
216+ [NSPredicate predicateWithFormat: @" identifier BEGINSWITH 'flutter_view'" ];
217+ XCUIElement* flutterView = [[app descendantsMatchingType: XCUIElementTypeAny]
218+ elementMatchingPredicate: predicateToFindFlutterView];
219+ if (![flutterView waitForExistenceWithTimeout: kSecondsToWaitForFlutterView ]) {
220+ NSLog (@" %@ " , app.debugDescription );
221+ XCTFail (@" Failed due to not able to find any flutterView with %@ seconds" ,
222+ @(kSecondsToWaitForFlutterView ));
223+ }
224+
225+ XCTAssertNotNil (flutterView);
226+
227+ SEL scroll = @selector (scrollByDeltaX:deltaY: );
228+ XCTAssertTrue ([flutterView respondsToSelector: scroll],
229+ @" If supportsPointerInteraction is true, this should be true too." );
230+ // Need to use NSInvocation in order to send primitive arguments to selector
231+ NSInvocation * invocation = [NSInvocation
232+ invocationWithMethodSignature: [XCUIElement instanceMethodSignatureForSelector: scroll]];
233+ [invocation setSelector: scroll];
234+ CGFloat deltaX = 0.0 ;
235+ CGFloat deltaY = 1000.0 ;
236+ [invocation setArgument: &deltaX atIndex: 2 ];
237+ [invocation setArgument: &deltaY atIndex: 3 ];
238+ [invocation invokeWithTarget: flutterView];
239+
240+ // The hover pointer is removed after ~3.5 seconds, this ensures that all events are received
241+ XCTestExpectation* sleepExpectation = [self expectationWithDescription: @" never fires" ];
242+ sleepExpectation.inverted = true ;
243+ [self waitForExpectations: @[ sleepExpectation ] timeout: 5.0 ];
244+
245+ // There are hover events interspersed with the scroll events in a varying order
246+ // Ensure the individual orderings are respected without hardcoding the absolute sequence
247+ NSMutableDictionary <NSString *, NSMutableArray <NSNumber *>*>* messages =
248+ [[NSMutableDictionary alloc ] init ];
249+ for (XCUIElement* element in [app.textFields allElementsBoundByIndex ]) {
250+ NSString * rawMessage = element.value ;
251+ // Parse out the sequence number
252+ NSUInteger commaIndex = [rawMessage rangeOfString: @" ," ].location ;
253+ NSInteger messageSequenceNumber =
254+ [rawMessage substringWithRange: NSMakeRange (0 , commaIndex)].integerValue ;
255+ // Parse out the rest of the message
256+ NSString * message = [rawMessage
257+ substringWithRange: NSMakeRange (commaIndex + 1 , rawMessage.length - (commaIndex + 1 ))];
258+ NSMutableArray <NSNumber *>* messageSequenceNumberList = messages[message];
259+ if (messageSequenceNumberList == nil ) {
260+ messageSequenceNumberList = [[NSMutableArray alloc ] init ];
261+ messages[message] = messageSequenceNumberList;
262+ }
263+ [messageSequenceNumberList addObject: @(messageSequenceNumber)];
264+ }
265+ // The number of hover events is not consistent, there could be one or many
266+ int hoverAddedSequenceNumber =
267+ assertOneMessageAndGetSequenceNumber (messages, @" PointerChange.add,device=0,buttons=0" );
268+ NSMutableArray <NSNumber *>* hoverSequenceNumbers =
269+ messages[@" PointerChange.hover,device=0,buttons=0" ];
270+ int hoverRemovedSequenceNumber =
271+ assertOneMessageAndGetSequenceNumber (messages, @" PointerChange.remove,device=0,buttons=0" );
272+ int panZoomAddedSequenceNumber =
273+ assertOneMessageAndGetSequenceNumber (messages, @" PointerChange.add,device=1,buttons=0" );
274+ int panZoomStartSequenceNumber = assertOneMessageAndGetSequenceNumber (
275+ messages, @" PointerChange.panZoomStart,device=1,buttons=0" );
276+ // The number of pan/zoom update events is not consistent, there could be one or many
277+ NSMutableArray <NSNumber *>* panZoomUpdateSequenceNumbers =
278+ messages[@" PointerChange.panZoomUpdate,device=1,buttons=0" ];
279+ int panZoomEndSequenceNumber = assertOneMessageAndGetSequenceNumber (
280+ messages, @" PointerChange.panZoomEnd,device=1,buttons=0" );
281+
282+ XCTAssertGreaterThan (panZoomStartSequenceNumber, panZoomAddedSequenceNumber,
283+ @" PanZoomStart occured before pointer was added" );
284+ XCTAssertGreaterThan ([[panZoomUpdateSequenceNumbers firstObject ] intValue ],
285+ panZoomStartSequenceNumber, @" PanZoomUpdate occured before PanZoomStart" );
286+ XCTAssertGreaterThan (panZoomEndSequenceNumber,
287+ [[panZoomUpdateSequenceNumbers lastObject ] intValue ],
288+ @" PanZoomUpdate occured after PanZoomUpdate" );
289+
290+ XCTAssertGreaterThan ([[hoverSequenceNumbers firstObject ] intValue ], hoverAddedSequenceNumber,
291+ @" Hover occured before pointer was added" );
292+ XCTAssertGreaterThan (hoverRemovedSequenceNumber, [[hoverSequenceNumbers lastObject ] intValue ],
293+ @" Hover occured after pointer was removed" );
294+ }
199295#pragma clang diagnostic pop
200296
201297@end
0 commit comments