chrome_render_widget_host_view_mac_history_swiper_unit_test.mm revision 0529e5d033099cbfc42635f6f6183833b09dff6e
1// Copyright 2013 The Chromium 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#include "testing/gtest/include/gtest/gtest.h" 6#import "chrome/browser/ui/cocoa/cocoa_test_helper.h" 7 8#include "base/mac/scoped_nsobject.h" 9 10#import "base/mac/sdk_forward_declarations.h" 11#import "chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h" 12#import "third_party/ocmock/OCMock/OCMock.h" 13#import "third_party/ocmock/ocmock_extensions.h" 14 15@interface HistorySwiper (MacHistorySwiperTest) 16- (BOOL)systemSettingsAllowHistorySwiping:(NSEvent*)event; 17- (BOOL)browserCanNavigateInDirection: 18 (history_swiper::NavigationDirection)forward 19 event:(NSEvent*)event; 20- (void)endHistorySwipe; 21- (void)beginHistorySwipeInDirection: 22 (history_swiper::NavigationDirection)goForward 23 event:(NSEvent*)event; 24- (void)navigateBrowserInDirection:(history_swiper::NavigationDirection)forward; 25- (void)initiateMagicMouseHistorySwipe:(BOOL)isRightScroll 26 event:(NSEvent*)event; 27@end 28 29class MacHistorySwiperTest : public CocoaTest { 30 public: 31 virtual void SetUp() OVERRIDE { 32 CocoaTest::SetUp(); 33 34 [HistorySwiper resetMagicMouseState]; 35 36 view_ = [[NSView alloc] init]; 37 id mockDelegate = 38 [OCMockObject mockForProtocol:@protocol(HistorySwiperDelegate)]; 39 [[[mockDelegate stub] andReturn:view_] viewThatWantsHistoryOverlay]; 40 [[[mockDelegate stub] andReturnBool:YES] shouldAllowHistorySwiping]; 41 42 base::scoped_nsobject<HistorySwiper> historySwiper( 43 [[HistorySwiper alloc] initWithDelegate:mockDelegate]); 44 id mockHistorySwiper = [OCMockObject partialMockForObject:historySwiper]; 45 [[[mockHistorySwiper stub] andReturnBool:YES] 46 systemSettingsAllowHistorySwiping:[OCMArg any]]; 47 [[[mockHistorySwiper stub] andReturnBool:YES] 48 browserCanNavigateInDirection:history_swiper::kForwards 49 event:[OCMArg any]]; 50 [[[mockHistorySwiper stub] andReturnBool:YES] 51 browserCanNavigateInDirection:history_swiper::kBackwards 52 event:[OCMArg any]]; 53 [[[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 54 ++begin_count_; 55 // beginHistorySwipeInDirection: calls endHistorySwipe internally. 56 --end_count_; 57 }] andForwardToRealObject] 58 beginHistorySwipeInDirection:history_swiper::kForwards 59 event:[OCMArg any]]; 60 [[[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 61 ++begin_count_; 62 // beginHistorySwipeInDirection: calls endHistorySwipe internally. 63 --end_count_; 64 }] andForwardToRealObject] 65 beginHistorySwipeInDirection:history_swiper::kBackwards 66 event:[OCMArg any]]; 67 [[[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 68 ++end_count_; 69 }] andForwardToRealObject] endHistorySwipe]; 70 [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 71 navigated_right_ = true; 72 }] navigateBrowserInDirection:history_swiper::kForwards]; 73 [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 74 navigated_left_ = true; 75 }] navigateBrowserInDirection:history_swiper::kBackwards]; 76 77 [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 78 magic_mouse_history_swipe_ = true; 79 }] initiateMagicMouseHistorySwipe:YES event:[OCMArg any]]; 80 [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 81 magic_mouse_history_swipe_ = true; 82 }] initiateMagicMouseHistorySwipe:NO event:[OCMArg any]]; 83 84 historySwiper_ = [mockHistorySwiper retain]; 85 86 begin_count_ = 0; 87 end_count_ = 0; 88 navigated_right_ = false; 89 navigated_left_ = false; 90 magic_mouse_history_swipe_ = false; 91 } 92 93 virtual void TearDown() OVERRIDE { 94 [view_ release]; 95 [historySwiper_ release]; 96 CocoaTest::TearDown(); 97 } 98 99 void startGestureInMiddle(); 100 void moveGestureInMiddle(); 101 void moveGestureAtPoint(NSPoint point); 102 void momentumMoveGestureAtPoint(NSPoint point); 103 void endGestureAtPoint(NSPoint point); 104 105 HistorySwiper* historySwiper_; 106 NSView* view_; 107 int begin_count_; 108 int end_count_; 109 bool navigated_right_; 110 bool navigated_left_; 111 bool magic_mouse_history_swipe_; 112}; 113 114NSPoint makePoint(CGFloat x, CGFloat y) { 115 NSPoint point; 116 point.x = x; 117 point.y = y; 118 return point; 119} 120 121id mockEventWithPoint(NSPoint point, NSEventType type) { 122 id mockEvent = [OCMockObject mockForClass:[NSEvent class]]; 123 id mockTouch = [OCMockObject mockForClass:[NSTouch class]]; 124 [[[mockTouch stub] andReturnNSPoint:point] normalizedPosition]; 125 NSArray* touches = @[mockTouch]; 126 [[[mockEvent stub] andReturn:touches] touchesMatchingPhase:NSTouchPhaseAny 127 inView:[OCMArg any]]; 128 [[[mockEvent stub] andReturnBool:NO] isDirectionInvertedFromDevice]; 129 [(NSEvent*)[[mockEvent stub] andReturnValue:OCMOCK_VALUE(type)] type]; 130 131 return mockEvent; 132} 133 134id scrollWheelEventWithPhase(NSEventPhase phase, 135 NSEventPhase momentumPhase, 136 CGFloat scrollingDeltaX, 137 CGFloat scrollingDeltaY) { 138 // The point isn't used, so we pass in bogus data. 139 id event = mockEventWithPoint(makePoint(0,0), NSScrollWheel); 140 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(phase)] phase]; 141 [(NSEvent*) 142 [[event stub] andReturnValue:OCMOCK_VALUE(momentumPhase)] momentumPhase]; 143 [(NSEvent*)[[event stub] 144 andReturnValue:OCMOCK_VALUE(scrollingDeltaX)] scrollingDeltaX]; 145 [(NSEvent*)[[event stub] 146 andReturnValue:OCMOCK_VALUE(scrollingDeltaY)] scrollingDeltaY]; 147 return event; 148} 149 150id scrollWheelEventWithPhase(NSEventPhase phase, 151 NSEventPhase momentumPhase) { 152 return scrollWheelEventWithPhase(phase, momentumPhase, 0, 0); 153} 154 155id scrollWheelEventWithPhase(NSEventPhase phase) { 156 return scrollWheelEventWithPhase(phase, NSEventPhaseNone); 157} 158 159void MacHistorySwiperTest::startGestureInMiddle() { 160 NSEvent* event = mockEventWithPoint(makePoint(0.5, 0.5), NSEventTypeGesture); 161 [historySwiper_ touchesBeganWithEvent:event]; 162 [historySwiper_ beginGestureWithEvent:event]; 163 NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseBegan); 164 [historySwiper_ handleEvent:scrollEvent]; 165} 166 167void MacHistorySwiperTest::moveGestureInMiddle() { 168 moveGestureAtPoint(makePoint(0.5, 0.5)); 169 170 // Callbacks from blink to set the relevant state for history swiping. 171 [historySwiper_ gotUnhandledWheelEvent]; 172 [historySwiper_ scrollOffsetPinnedToLeft:YES toRight:YES]; 173 [historySwiper_ setHasHorizontalScrollbar:NO]; 174} 175 176void MacHistorySwiperTest::moveGestureAtPoint(NSPoint point) { 177 NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture); 178 [historySwiper_ touchesMovedWithEvent:event]; 179 180 NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseChanged); 181 [historySwiper_ handleEvent:scrollEvent]; 182} 183 184void MacHistorySwiperTest::momentumMoveGestureAtPoint(NSPoint point) { 185 NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture); 186 [historySwiper_ touchesMovedWithEvent:event]; 187 188 NSEvent* scrollEvent = 189 scrollWheelEventWithPhase(NSEventPhaseNone, NSEventPhaseChanged); 190 [historySwiper_ handleEvent:scrollEvent]; 191} 192 193void MacHistorySwiperTest::endGestureAtPoint(NSPoint point) { 194 NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture); 195 [historySwiper_ touchesEndedWithEvent:event]; 196 197 NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseEnded); 198 [historySwiper_ handleEvent:scrollEvent]; 199} 200 201// Test that a simple left-swipe causes navigation. 202TEST_F(MacHistorySwiperTest, SwipeLeft) { 203 // These tests require 10.7+ APIs. 204 if (![NSEvent 205 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 206 return; 207 208 startGestureInMiddle(); 209 moveGestureInMiddle(); 210 211 EXPECT_EQ(begin_count_, 0); 212 EXPECT_EQ(end_count_, 0); 213 214 moveGestureAtPoint(makePoint(0.2, 0.5)); 215 EXPECT_EQ(begin_count_, 1); 216 EXPECT_EQ(end_count_, 0); 217 EXPECT_FALSE(navigated_right_); 218 EXPECT_FALSE(navigated_left_); 219 220 endGestureAtPoint(makePoint(0.2, 0.5)); 221 EXPECT_EQ(begin_count_, 1); 222 EXPECT_EQ(end_count_, 1); 223 EXPECT_FALSE(navigated_right_); 224 EXPECT_TRUE(navigated_left_); 225} 226 227// Test that a simple right-swipe causes navigation. 228TEST_F(MacHistorySwiperTest, SwipeRight) { 229 // These tests require 10.7+ APIs. 230 if (![NSEvent 231 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 232 return; 233 234 startGestureInMiddle(); 235 moveGestureInMiddle(); 236 237 EXPECT_EQ(begin_count_, 0); 238 EXPECT_EQ(end_count_, 0); 239 240 moveGestureAtPoint(makePoint(0.8, 0.5)); 241 EXPECT_EQ(begin_count_, 1); 242 EXPECT_EQ(end_count_, 0); 243 EXPECT_FALSE(navigated_right_); 244 EXPECT_FALSE(navigated_left_); 245 246 endGestureAtPoint(makePoint(0.8, 0.5)); 247 EXPECT_EQ(begin_count_, 1); 248 EXPECT_EQ(end_count_, 1); 249 EXPECT_TRUE(navigated_right_); 250 EXPECT_FALSE(navigated_left_); 251} 252 253// If the user doesn't swipe enough, the history swiper should begin, but the 254// browser should not navigate. 255TEST_F(MacHistorySwiperTest, SwipeLeftSmallAmount) { 256 // These tests require 10.7+ APIs. 257 if (![NSEvent 258 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 259 return; 260 261 startGestureInMiddle(); 262 moveGestureInMiddle(); 263 moveGestureAtPoint(makePoint(0.45, 0.5)); 264 endGestureAtPoint(makePoint(0.45, 0.5)); 265 EXPECT_EQ(begin_count_, 1); 266 EXPECT_EQ(end_count_, 1); 267 EXPECT_FALSE(navigated_right_); 268 EXPECT_FALSE(navigated_left_); 269} 270 271// Diagonal swipes with a slight horizontal bias should not start the history 272// swiper. 273TEST_F(MacHistorySwiperTest, SwipeDiagonal) { 274 // These tests require 10.7+ APIs. 275 if (![NSEvent 276 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 277 return; 278 279 startGestureInMiddle(); 280 moveGestureInMiddle(); 281 moveGestureAtPoint(makePoint(0.6, 0.59)); 282 endGestureAtPoint(makePoint(0.6, 0.59)); 283 284 EXPECT_EQ(begin_count_, 0); 285 EXPECT_EQ(end_count_, 0); 286 EXPECT_FALSE(navigated_right_); 287 EXPECT_FALSE(navigated_left_); 288} 289 290// Swiping left and then down should cancel the history swiper without 291// navigating. 292TEST_F(MacHistorySwiperTest, SwipeLeftThenDown) { 293 // These tests require 10.7+ APIs. 294 if (![NSEvent 295 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 296 return; 297 298 startGestureInMiddle(); 299 moveGestureInMiddle(); 300 moveGestureAtPoint(makePoint(0.4, 0.5)); 301 moveGestureAtPoint(makePoint(0.4, 0.3)); 302 endGestureAtPoint(makePoint(0.2, 0.2)); 303 EXPECT_EQ(begin_count_, 1); 304 EXPECT_EQ(end_count_, 1); 305 EXPECT_FALSE(navigated_right_); 306 EXPECT_FALSE(navigated_left_); 307} 308 309// Sometimes Cocoa gets confused and sends us a momentum swipe event instead of 310// a swipe gesture event. We should still function appropriately. 311TEST_F(MacHistorySwiperTest, MomentumSwipeLeft) { 312 // These tests require 10.7+ APIs. 313 if (![NSEvent 314 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 315 return; 316 317 startGestureInMiddle(); 318 319 // Send a momentum move gesture. 320 momentumMoveGestureAtPoint(makePoint(0.5, 0.5)); 321 EXPECT_EQ(begin_count_, 0); 322 EXPECT_EQ(end_count_, 0); 323 324 // Callbacks from blink to set the relevant state for history swiping. 325 [historySwiper_ gotUnhandledWheelEvent]; 326 [historySwiper_ scrollOffsetPinnedToLeft:YES toRight:YES]; 327 [historySwiper_ setHasHorizontalScrollbar:NO]; 328 329 momentumMoveGestureAtPoint(makePoint(0.2, 0.5)); 330 EXPECT_EQ(begin_count_, 1); 331 EXPECT_EQ(end_count_, 0); 332 EXPECT_FALSE(navigated_right_); 333 EXPECT_FALSE(navigated_left_); 334 335 endGestureAtPoint(makePoint(0.2, 0.5)); 336 EXPECT_EQ(begin_count_, 1); 337 EXPECT_EQ(end_count_, 1); 338 EXPECT_FALSE(navigated_right_); 339 EXPECT_TRUE(navigated_left_); 340} 341 342// Momentum scroll events for magic mouse should not attempt to trigger the 343// `trackSwipeEventWithOptions:` api, as that throws an exception. 344TEST_F(MacHistorySwiperTest, MagicMouseMomentumSwipe) { 345 // These tests require 10.7+ APIs. 346 if (![NSEvent 347 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 348 return; 349 350 // Magic mouse events don't generate 'touches*' callbacks. 351 NSEvent* event = mockEventWithPoint(makePoint(0.5, 0.5), NSEventTypeGesture); 352 [historySwiper_ beginGestureWithEvent:event]; 353 NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseBegan); 354 [historySwiper_ handleEvent:scrollEvent]; 355 356 // Callbacks from blink to set the relevant state for history swiping. 357 [historySwiper_ gotUnhandledWheelEvent]; 358 [historySwiper_ scrollOffsetPinnedToLeft:YES toRight:YES]; 359 [historySwiper_ setHasHorizontalScrollbar:NO]; 360 361 // Send a momentum move gesture. 362 scrollEvent = 363 scrollWheelEventWithPhase(NSEventPhaseNone, NSEventPhaseChanged, 5.0, 0); 364 [historySwiper_ handleEvent:scrollEvent]; 365 366 EXPECT_FALSE(magic_mouse_history_swipe_); 367} 368 369// User starts a swipe but doesn't move. 370TEST_F(MacHistorySwiperTest, NoSwipe) { 371 // These tests require 10.7+ APIs. 372 if (![NSEvent 373 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 374 return; 375 376 startGestureInMiddle(); 377 moveGestureInMiddle(); 378 moveGestureAtPoint(makePoint(0.5, 0.5)); 379 endGestureAtPoint(makePoint(0.5, 0.5)); 380 381 EXPECT_EQ(begin_count_, 0); 382 EXPECT_EQ(end_count_, 0); 383} 384 385// After a gesture is successfully recognized, momentum events should be 386// swallowed, but new events should pass through. 387TEST_F(MacHistorySwiperTest, TouchEventAfterGestureFinishes) { 388 // These tests require 10.7+ APIs. 389 if (![NSEvent 390 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 391 return; 392 393 // Successfully pass through a gesture. 394 startGestureInMiddle(); 395 moveGestureInMiddle(); 396 moveGestureAtPoint(makePoint(0.8, 0.5)); 397 endGestureAtPoint(makePoint(0.8, 0.5)); 398 EXPECT_TRUE(navigated_right_); 399 400 // Momentum events should be swallowed. 401 NSEvent* momentumEvent = scrollWheelEventWithPhase(NSEventPhaseNone, 402 NSEventPhaseChanged); 403 EXPECT_TRUE([historySwiper_ handleEvent:momentumEvent]); 404 405 // New events should not be swallowed. 406 NSEvent* beganEvent = scrollWheelEventWithPhase(NSEventPhaseBegan); 407 EXPECT_FALSE([historySwiper_ handleEvent:beganEvent]); 408} 409