@@ -75,13 +75,16 @@ - (void)testPointerButtons {
75
75
XCTAssertTrue (
76
76
[app.textFields[@" 2,PointerChange.up,device=0,buttons=0" ] waitForExistenceWithTimeout: 1 ],
77
77
@" 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" );
78
81
SEL rightClick = @selector (rightClick );
79
82
XCTAssertTrue ([flutterView respondsToSelector: rightClick],
80
83
@" If supportsPointerInteraction is true, this should be true too." );
81
84
[flutterView performSelector: rightClick];
82
85
// On simulated right click, a hover also occurs, so the hover pointer is added
83
86
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 ],
85
88
@" PointerChange.add event did not occur for a right-click's hover pointer" );
86
89
87
90
// The hover pointer is removed after ~3.5 seconds, this ensures that all events are received
@@ -137,7 +140,7 @@ - (void)testPointerButtons {
137
140
@" Right-click pointer was pressed before it was added" );
138
141
XCTAssertGreaterThan (rightClickUpSequenceNumber, rightClickDownSequenceNumber,
139
142
@" Right-click pointer was released before it was pressed" );
140
- XCTAssertGreaterThan ([[hoverSequenceNumbers firstObject ] intValue ], 3 ,
143
+ XCTAssertGreaterThan ([[hoverSequenceNumbers firstObject ] intValue ], 4 ,
141
144
@" Hover occured before hover pointer was added" );
142
145
XCTAssertGreaterThan (hoverRemovedSequenceNumber, [[hoverSequenceNumbers lastObject ] intValue ],
143
146
@" Hover occured after hover pointer was removed" );
@@ -196,6 +199,99 @@ - (void)testPointerHover {
196
199
XCTAssertTrue ([app.textFields[removeMessage] waitForExistenceWithTimeout: 1 ],
197
200
@" PointerChange.remove event did not occur for a hover" );
198
201
}
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
+ }
199
295
#pragma clang diagnostic pop
200
296
201
297
@end
0 commit comments