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