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