chrome_render_widget_host_view_mac_history_swiper_unit_test.mm revision c5cede9ae108bb15f6b7a8aea21c7e1fefa2834c
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_ gotWheelEventConsumed:NO];
172}
173
174void MacHistorySwiperTest::moveGestureAtPoint(NSPoint point) {
175  NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture);
176  [historySwiper_ touchesMovedWithEvent:event];
177
178  NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseChanged);
179  [historySwiper_ handleEvent:scrollEvent];
180}
181
182void MacHistorySwiperTest::momentumMoveGestureAtPoint(NSPoint point) {
183  NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture);
184  [historySwiper_ touchesMovedWithEvent:event];
185
186  NSEvent* scrollEvent =
187      scrollWheelEventWithPhase(NSEventPhaseNone, NSEventPhaseChanged);
188  [historySwiper_ handleEvent:scrollEvent];
189}
190
191void MacHistorySwiperTest::endGestureAtPoint(NSPoint point) {
192  NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture);
193  [historySwiper_ touchesEndedWithEvent:event];
194
195  NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseEnded);
196  [historySwiper_ handleEvent:scrollEvent];
197}
198
199// Test that a simple left-swipe causes navigation.
200TEST_F(MacHistorySwiperTest, SwipeLeft) {
201  // These tests require 10.7+ APIs.
202  if (![NSEvent
203          respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
204    return;
205
206  startGestureInMiddle();
207  moveGestureInMiddle();
208
209  EXPECT_EQ(begin_count_, 0);
210  EXPECT_EQ(end_count_, 0);
211
212  moveGestureAtPoint(makePoint(0.2, 0.5));
213  EXPECT_EQ(begin_count_, 1);
214  EXPECT_EQ(end_count_, 0);
215  EXPECT_FALSE(navigated_right_);
216  EXPECT_FALSE(navigated_left_);
217
218  endGestureAtPoint(makePoint(0.2, 0.5));
219  EXPECT_EQ(begin_count_, 1);
220  EXPECT_EQ(end_count_, 1);
221  EXPECT_FALSE(navigated_right_);
222  EXPECT_TRUE(navigated_left_);
223}
224
225// Test that a simple right-swipe causes navigation.
226TEST_F(MacHistorySwiperTest, SwipeRight) {
227  // These tests require 10.7+ APIs.
228  if (![NSEvent
229          respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
230    return;
231
232  startGestureInMiddle();
233  moveGestureInMiddle();
234
235  EXPECT_EQ(begin_count_, 0);
236  EXPECT_EQ(end_count_, 0);
237
238  moveGestureAtPoint(makePoint(0.8, 0.5));
239  EXPECT_EQ(begin_count_, 1);
240  EXPECT_EQ(end_count_, 0);
241  EXPECT_FALSE(navigated_right_);
242  EXPECT_FALSE(navigated_left_);
243
244  endGestureAtPoint(makePoint(0.8, 0.5));
245  EXPECT_EQ(begin_count_, 1);
246  EXPECT_EQ(end_count_, 1);
247  EXPECT_TRUE(navigated_right_);
248  EXPECT_FALSE(navigated_left_);
249}
250
251// If the user doesn't swipe enough, the history swiper should begin, but the
252// browser should not navigate.
253TEST_F(MacHistorySwiperTest, SwipeLeftSmallAmount) {
254  // These tests require 10.7+ APIs.
255  if (![NSEvent
256          respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
257    return;
258
259  startGestureInMiddle();
260  moveGestureInMiddle();
261  moveGestureAtPoint(makePoint(0.45, 0.5));
262  endGestureAtPoint(makePoint(0.45, 0.5));
263  EXPECT_EQ(begin_count_, 1);
264  EXPECT_EQ(end_count_, 1);
265  EXPECT_FALSE(navigated_right_);
266  EXPECT_FALSE(navigated_left_);
267}
268
269// Diagonal swipes with a slight horizontal bias should not start the history
270// swiper.
271TEST_F(MacHistorySwiperTest, SwipeDiagonal) {
272  // These tests require 10.7+ APIs.
273  if (![NSEvent
274          respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
275    return;
276
277  startGestureInMiddle();
278  moveGestureInMiddle();
279  moveGestureAtPoint(makePoint(0.6, 0.59));
280  endGestureAtPoint(makePoint(0.6, 0.59));
281
282  EXPECT_EQ(begin_count_, 0);
283  EXPECT_EQ(end_count_, 0);
284  EXPECT_FALSE(navigated_right_);
285  EXPECT_FALSE(navigated_left_);
286}
287
288// Swiping left and then down should cancel the history swiper without
289// navigating.
290TEST_F(MacHistorySwiperTest, SwipeLeftThenDown) {
291  // These tests require 10.7+ APIs.
292  if (![NSEvent
293          respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
294    return;
295
296  startGestureInMiddle();
297  moveGestureInMiddle();
298  moveGestureAtPoint(makePoint(0.4, 0.5));
299  moveGestureAtPoint(makePoint(0.4, 0.3));
300  endGestureAtPoint(makePoint(0.2, 0.2));
301  EXPECT_EQ(begin_count_, 1);
302  EXPECT_EQ(end_count_, 1);
303  EXPECT_FALSE(navigated_right_);
304  EXPECT_FALSE(navigated_left_);
305}
306
307// Sometimes Cocoa gets confused and sends us a momentum swipe event instead of
308// a swipe gesture event. We should still function appropriately.
309TEST_F(MacHistorySwiperTest, MomentumSwipeLeft) {
310  // These tests require 10.7+ APIs.
311  if (![NSEvent
312          respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
313    return;
314
315  startGestureInMiddle();
316
317  // Send a momentum move gesture.
318  momentumMoveGestureAtPoint(makePoint(0.5, 0.5));
319  EXPECT_EQ(begin_count_, 0);
320  EXPECT_EQ(end_count_, 0);
321
322  // Callbacks from blink to set the relevant state for history swiping.
323  [historySwiper_ gotWheelEventConsumed:NO];
324
325  momentumMoveGestureAtPoint(makePoint(0.2, 0.5));
326  EXPECT_EQ(begin_count_, 1);
327  EXPECT_EQ(end_count_, 0);
328  EXPECT_FALSE(navigated_right_);
329  EXPECT_FALSE(navigated_left_);
330
331  endGestureAtPoint(makePoint(0.2, 0.5));
332  EXPECT_EQ(begin_count_, 1);
333  EXPECT_EQ(end_count_, 1);
334  EXPECT_FALSE(navigated_right_);
335  EXPECT_TRUE(navigated_left_);
336}
337
338// Momentum scroll events for magic mouse should not attempt to trigger the
339// `trackSwipeEventWithOptions:` api, as that throws an exception.
340TEST_F(MacHistorySwiperTest, MagicMouseMomentumSwipe) {
341  // These tests require 10.7+ APIs.
342  if (![NSEvent
343          respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
344    return;
345
346  // Magic mouse events don't generate 'touches*' callbacks.
347  NSEvent* event = mockEventWithPoint(makePoint(0.5, 0.5), NSEventTypeGesture);
348  [historySwiper_ beginGestureWithEvent:event];
349  NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseBegan);
350  [historySwiper_ handleEvent:scrollEvent];
351
352  // Callbacks from blink to set the relevant state for history swiping.
353  [historySwiper_ gotWheelEventConsumed:NO];
354
355  // Send a momentum move gesture.
356  scrollEvent =
357      scrollWheelEventWithPhase(NSEventPhaseNone, NSEventPhaseChanged, 5.0, 0);
358  [historySwiper_ handleEvent:scrollEvent];
359
360  EXPECT_FALSE(magic_mouse_history_swipe_);
361}
362
363// User starts a swipe but doesn't move.
364TEST_F(MacHistorySwiperTest, NoSwipe) {
365  // These tests require 10.7+ APIs.
366  if (![NSEvent
367          respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
368    return;
369
370  startGestureInMiddle();
371  moveGestureInMiddle();
372  moveGestureAtPoint(makePoint(0.5, 0.5));
373  endGestureAtPoint(makePoint(0.5, 0.5));
374
375  EXPECT_EQ(begin_count_, 0);
376  EXPECT_EQ(end_count_, 0);
377}
378
379// After a gesture is successfully recognized, momentum events should be
380// swallowed, but new events should pass through.
381TEST_F(MacHistorySwiperTest, TouchEventAfterGestureFinishes) {
382  // These tests require 10.7+ APIs.
383  if (![NSEvent
384          respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
385    return;
386
387  // Successfully pass through a gesture.
388  startGestureInMiddle();
389  moveGestureInMiddle();
390  moveGestureAtPoint(makePoint(0.8, 0.5));
391  endGestureAtPoint(makePoint(0.8, 0.5));
392  EXPECT_TRUE(navigated_right_);
393
394  // Momentum events should be swallowed.
395  NSEvent* momentumEvent = scrollWheelEventWithPhase(NSEventPhaseNone,
396                                                     NSEventPhaseChanged);
397  EXPECT_TRUE([historySwiper_ handleEvent:momentumEvent]);
398
399  // New events should not be swallowed.
400  NSEvent* beganEvent = scrollWheelEventWithPhase(NSEventPhaseBegan);
401  EXPECT_FALSE([historySwiper_ handleEvent:beganEvent]);
402}
403
404// If any event is handled by blink, history swiping should not trigger.
405TEST_F(MacHistorySwiperTest, EventHandledByBlink) {
406  // These tests require 10.7+ APIs.
407  if (![NSEvent
408          respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)])
409    return;
410
411  startGestureInMiddle();
412  moveGestureInMiddle();
413
414  // An event is handled by blink.
415  [historySwiper_ gotWheelEventConsumed:YES];
416
417  // A new event comes in, that isn't handled by blink.
418  moveGestureAtPoint(makePoint(0.2, 0.5));
419  [historySwiper_ gotWheelEventConsumed:NO];
420
421  EXPECT_EQ(begin_count_, 0);
422}
423