Home | History | Annotate | Download | only in toolbar
      1 // Copyright (c) 2011 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 #import <Cocoa/Cocoa.h>
      6 
      7 #import "chrome/browser/ui/cocoa/toolbar/reload_button.h"
      8 
      9 #include "base/mac/scoped_nsobject.h"
     10 #include "chrome/app/chrome_command_ids.h"
     11 #import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
     12 #import "chrome/browser/ui/cocoa/image_button_cell.h"
     13 #import "testing/gtest_mac.h"
     14 #include "testing/platform_test.h"
     15 #import "third_party/ocmock/OCMock/OCMock.h"
     16 #import "ui/base/test/cocoa_test_event_utils.h"
     17 
     18 @interface ReloadButton (Testing)
     19 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds;
     20 @end
     21 
     22 @protocol TargetActionMock <NSObject>
     23 - (void)anAction:(id)sender;
     24 @end
     25 
     26 namespace {
     27 
     28 class ReloadButtonTest : public CocoaTest {
     29  public:
     30   ReloadButtonTest() {
     31     NSRect frame = NSMakeRect(0, 0, 20, 20);
     32     base::scoped_nsobject<ReloadButton> button(
     33         [[ReloadButton alloc] initWithFrame:frame]);
     34     button_ = button.get();
     35 
     36     // Set things up so unit tests have a reliable baseline.
     37     [button_ setTag:IDC_RELOAD];
     38     [button_ awakeFromNib];
     39 
     40     [[test_window() contentView] addSubview:button_];
     41   }
     42 
     43   bool IsMouseInside() {
     44     return [[button_ cell] isMouseInside];
     45   }
     46 
     47   void MouseEnter() {
     48     [[button_ cell] mouseEntered:nil];
     49   }
     50 
     51   void MouseExit() {
     52     [[button_ cell] mouseExited:nil];
     53   }
     54 
     55   ReloadButton* button_;
     56 };
     57 
     58 TEST_VIEW(ReloadButtonTest, button_)
     59 
     60 // Test that mouse-tracking is setup and does the right thing.
     61 TEST_F(ReloadButtonTest, IsMouseInside) {
     62   EXPECT_FALSE(IsMouseInside());
     63   MouseEnter();
     64   EXPECT_TRUE(IsMouseInside());
     65   MouseExit();
     66 }
     67 
     68 // Verify that multiple clicks do not result in multiple messages to
     69 // the target.
     70 TEST_F(ReloadButtonTest, IgnoredMultiClick) {
     71   id mock_target = [OCMockObject mockForProtocol:@protocol(TargetActionMock)];
     72   [button_ setTarget:mock_target];
     73   [button_ setAction:@selector(anAction:)];
     74 
     75   // Expect the action once.
     76   [[mock_target expect] anAction:button_];
     77 
     78   const std::pair<NSEvent*,NSEvent*> click_one =
     79       cocoa_test_event_utils::MouseClickInView(button_, 1);
     80   const std::pair<NSEvent*,NSEvent*> click_two =
     81       cocoa_test_event_utils::MouseClickInView(button_, 2);
     82   [NSApp postEvent:click_one.second atStart:YES];
     83   [button_ mouseDown:click_one.first];
     84   [NSApp postEvent:click_two.second atStart:YES];
     85   [button_ mouseDown:click_two.first];
     86 
     87   [button_ setTarget:nil];
     88 }
     89 
     90 TEST_F(ReloadButtonTest, UpdateTag) {
     91   [button_ setTag:IDC_STOP];
     92 
     93   [button_ updateTag:IDC_RELOAD];
     94   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
     95   NSString* const reloadToolTip = [button_ toolTip];
     96 
     97   [button_ updateTag:IDC_STOP];
     98   EXPECT_EQ(IDC_STOP, [button_ tag]);
     99   NSString* const stopToolTip = [button_ toolTip];
    100   EXPECT_NSNE(reloadToolTip, stopToolTip);
    101 
    102   [button_ updateTag:IDC_RELOAD];
    103   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    104   EXPECT_NSEQ(reloadToolTip, [button_ toolTip]);
    105 }
    106 
    107 // Test that when forcing the mode, it takes effect immediately,
    108 // regardless of whether the mouse is hovering.
    109 TEST_F(ReloadButtonTest, SetIsLoadingForce) {
    110   EXPECT_FALSE(IsMouseInside());
    111   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    112 
    113   // Changes to stop immediately.
    114   [button_ setIsLoading:YES force:YES];
    115   EXPECT_EQ(IDC_STOP, [button_ tag]);
    116 
    117   // Changes to reload immediately.
    118   [button_ setIsLoading:NO force:YES];
    119   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    120 
    121   // Changes to stop immediately when the mouse is hovered, and
    122   // doesn't change when the mouse exits.
    123   MouseEnter();
    124   EXPECT_TRUE(IsMouseInside());
    125   [button_ setIsLoading:YES force:YES];
    126   EXPECT_EQ(IDC_STOP, [button_ tag]);
    127   MouseExit();
    128   EXPECT_FALSE(IsMouseInside());
    129   EXPECT_EQ(IDC_STOP, [button_ tag]);
    130 
    131   // Changes to reload immediately when the mouse is hovered, and
    132   // doesn't change when the mouse exits.
    133   MouseEnter();
    134   EXPECT_TRUE(IsMouseInside());
    135   [button_ setIsLoading:NO force:YES];
    136   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    137   MouseExit();
    138   EXPECT_FALSE(IsMouseInside());
    139   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    140 }
    141 
    142 // Test that without force, stop mode is set immediately, but reload
    143 // is affected by the hover status.
    144 TEST_F(ReloadButtonTest, SetIsLoadingNoForceUnHover) {
    145   EXPECT_FALSE(IsMouseInside());
    146   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    147 
    148   // Changes to stop immediately when the mouse is not hovering.
    149   [button_ setIsLoading:YES force:NO];
    150   EXPECT_EQ(IDC_STOP, [button_ tag]);
    151 
    152   // Changes to reload immediately when the mouse is not hovering.
    153   [button_ setIsLoading:NO force:NO];
    154   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    155 
    156   // Changes to stop immediately when the mouse is hovered, and
    157   // doesn't change when the mouse exits.
    158   MouseEnter();
    159   EXPECT_TRUE(IsMouseInside());
    160   [button_ setIsLoading:YES force:NO];
    161   EXPECT_EQ(IDC_STOP, [button_ tag]);
    162   MouseExit();
    163   EXPECT_FALSE(IsMouseInside());
    164   EXPECT_EQ(IDC_STOP, [button_ tag]);
    165 
    166   // Does not change to reload immediately when the mouse is hovered,
    167   // changes when the mouse exits.
    168   MouseEnter();
    169   EXPECT_TRUE(IsMouseInside());
    170   [button_ setIsLoading:NO force:NO];
    171   EXPECT_EQ(IDC_STOP, [button_ tag]);
    172   MouseExit();
    173   EXPECT_FALSE(IsMouseInside());
    174   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    175 }
    176 
    177 // Test that without force, stop mode is set immediately, and reload
    178 // will be set after a timeout.
    179 // TODO(shess): Reenable, http://crbug.com/61485
    180 TEST_F(ReloadButtonTest, DISABLED_SetIsLoadingNoForceTimeout) {
    181   // When the event loop first spins, some delayed tracking-area setup
    182   // is done, which causes -mouseExited: to be called.  Spin it at
    183   // least once, and dequeue any pending events.
    184   // TODO(shess): It would be more reasonable to have an MockNSTimer
    185   // factory for the class to use, which this code could fire
    186   // directly.
    187   while ([NSApp nextEventMatchingMask:NSAnyEventMask
    188                             untilDate:nil
    189                                inMode:NSDefaultRunLoopMode
    190                               dequeue:YES]) {
    191   }
    192 
    193   const NSTimeInterval kShortTimeout = 0.1;
    194   [ReloadButton setPendingReloadTimeout:kShortTimeout];
    195 
    196   EXPECT_FALSE(IsMouseInside());
    197   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    198 
    199   // Move the mouse into the button and press it.
    200   MouseEnter();
    201   EXPECT_TRUE(IsMouseInside());
    202   [button_ setIsLoading:YES force:NO];
    203   EXPECT_EQ(IDC_STOP, [button_ tag]);
    204 
    205   // Does not change to reload immediately when the mouse is hovered.
    206   EXPECT_TRUE(IsMouseInside());
    207   [button_ setIsLoading:NO force:NO];
    208   EXPECT_TRUE(IsMouseInside());
    209   EXPECT_EQ(IDC_STOP, [button_ tag]);
    210   EXPECT_TRUE(IsMouseInside());
    211 
    212   // Spin event loop until the timeout passes.
    213   NSDate* pastTimeout = [NSDate dateWithTimeIntervalSinceNow:2 * kShortTimeout];
    214   [NSApp nextEventMatchingMask:NSAnyEventMask
    215                      untilDate:pastTimeout
    216                         inMode:NSDefaultRunLoopMode
    217                        dequeue:NO];
    218 
    219   // Mouse is still hovered, button is in reload mode.  If the mouse
    220   // is no longer hovered, see comment at top of function.
    221   EXPECT_TRUE(IsMouseInside());
    222   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    223 }
    224 
    225 // Test that pressing stop after reload mode has been requested
    226 // doesn't forward the stop message.
    227 TEST_F(ReloadButtonTest, StopAfterReloadSet) {
    228   id mock_target = [OCMockObject mockForProtocol:@protocol(TargetActionMock)];
    229   [button_ setTarget:mock_target];
    230   [button_ setAction:@selector(anAction:)];
    231 
    232   EXPECT_FALSE(IsMouseInside());
    233 
    234   // Get to stop mode.
    235   [button_ setIsLoading:YES force:YES];
    236   EXPECT_EQ(IDC_STOP, [button_ tag]);
    237   EXPECT_TRUE([button_ isEnabled]);
    238 
    239   // Expect the action once.
    240   [[mock_target expect] anAction:button_];
    241 
    242   // Clicking in stop mode should send the action and transition to
    243   // reload mode.
    244   const std::pair<NSEvent*,NSEvent*> click =
    245       cocoa_test_event_utils::MouseClickInView(button_, 1);
    246   [NSApp postEvent:click.second atStart:YES];
    247   [button_ mouseDown:click.first];
    248   EXPECT_EQ(IDC_RELOAD, [button_ tag]);
    249   EXPECT_TRUE([button_ isEnabled]);
    250 
    251   // Get back to stop mode.
    252   [button_ setIsLoading:YES force:YES];
    253   EXPECT_EQ(IDC_STOP, [button_ tag]);
    254   EXPECT_TRUE([button_ isEnabled]);
    255 
    256   // If hover prevented reload mode immediately taking effect, clicks should do
    257   // nothing, because the button should be disabled.
    258   MouseEnter();
    259   EXPECT_TRUE(IsMouseInside());
    260   [button_ setIsLoading:NO force:NO];
    261   EXPECT_EQ(IDC_STOP, [button_ tag]);
    262   EXPECT_FALSE([button_ isEnabled]);
    263   [NSApp postEvent:click.second atStart:YES];
    264   [button_ mouseDown:click.first];
    265   EXPECT_EQ(IDC_STOP, [button_ tag]);
    266 
    267   [button_ setTarget:nil];
    268 }
    269 
    270 }  // namespace
    271