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 #include <Cocoa/Cocoa.h> 6 7 #include "base/mac/scoped_nsobject.h" 8 #include "base/memory/scoped_ptr.h" 9 #include "base/message_loop/message_loop.h" 10 #include "base/strings/utf_string_conversions.h" 11 #import "chrome/browser/ui/cocoa/bubble_view.h" 12 #import "chrome/browser/ui/cocoa/cocoa_test_helper.h" 13 #import "chrome/browser/ui/cocoa/status_bubble_mac.h" 14 #include "testing/gtest/include/gtest/gtest.h" 15 #import "testing/gtest_mac.h" 16 #include "testing/platform_test.h" 17 #import "third_party/ocmock/OCMock/OCMock.h" 18 #include "ui/gfx/point.h" 19 #include "url/gurl.h" 20 21 using base::UTF8ToUTF16; 22 23 // The test delegate records all of the status bubble object's state 24 // transitions. 25 @interface StatusBubbleMacTestDelegate : NSObject { 26 @private 27 NSWindow* window_; // Weak. 28 NSPoint baseFrameOffset_; 29 std::vector<StatusBubbleMac::StatusBubbleState> states_; 30 } 31 - (id)initWithWindow:(NSWindow*)window; 32 - (void)forceBaseFrameOffset:(NSPoint)baseFrameOffset; 33 - (NSRect)statusBubbleBaseFrame; 34 - (void)statusBubbleWillEnterState:(StatusBubbleMac::StatusBubbleState)state; 35 @end 36 @implementation StatusBubbleMacTestDelegate 37 - (id)initWithWindow:(NSWindow*)window { 38 if ((self = [super init])) { 39 window_ = window; 40 baseFrameOffset_ = NSZeroPoint; 41 } 42 return self; 43 } 44 - (void)forceBaseFrameOffset:(NSPoint)baseFrameOffset { 45 baseFrameOffset_ = baseFrameOffset; 46 } 47 - (NSRect)statusBubbleBaseFrame { 48 NSView* contentView = [window_ contentView]; 49 NSRect baseFrame = [contentView convertRect:[contentView frame] toView:nil]; 50 if (baseFrameOffset_.x > 0 || baseFrameOffset_.y > 0) { 51 baseFrame = NSOffsetRect(baseFrame, baseFrameOffset_.x, baseFrameOffset_.y); 52 baseFrame.size.width -= baseFrameOffset_.x; 53 baseFrame.size.height -= baseFrameOffset_.y; 54 } 55 return baseFrame; 56 } 57 - (void)statusBubbleWillEnterState:(StatusBubbleMac::StatusBubbleState)state { 58 states_.push_back(state); 59 } 60 - (std::vector<StatusBubbleMac::StatusBubbleState>*)states { 61 return &states_; 62 } 63 @end 64 65 // This class implements, for testing purposes, a subclass of |StatusBubbleMac| 66 // whose |MouseMoved()| method does nothing. This lets the tests fake the mouse 67 // position and avoid being affected by the true mouse position. 68 class StatusBubbleMacIgnoreMouseMoved : public StatusBubbleMac { 69 public: 70 StatusBubbleMacIgnoreMouseMoved(NSWindow* parent, id delegate) 71 : StatusBubbleMac(parent, delegate), mouseLocation_(0, 0) { 72 // Set the fake mouse position to the top right of the content area. 73 NSRect contentBounds = [[parent contentView] bounds]; 74 mouseLocation_.SetPoint(NSMaxX(contentBounds), NSMaxY(contentBounds)); 75 } 76 77 virtual void MouseMoved( 78 const gfx::Point& location, 79 bool left_content) OVERRIDE { 80 } 81 82 virtual gfx::Point GetMouseLocation() OVERRIDE { 83 return mouseLocation_; 84 } 85 86 void SetMouseLocationForTesting(int x, int y) { 87 mouseLocation_.SetPoint(x, y); 88 StatusBubbleMac::MouseMoved(gfx::Point(x, y), false); 89 } 90 91 private: 92 gfx::Point mouseLocation_; 93 }; 94 95 class StatusBubbleMacTest : public CocoaTest { 96 public: 97 virtual void SetUp() { 98 CocoaTest::SetUp(); 99 NSWindow* window = test_window(); 100 EXPECT_TRUE(window); 101 delegate_.reset( 102 [[StatusBubbleMacTestDelegate alloc] initWithWindow: window]); 103 EXPECT_TRUE(delegate_.get()); 104 bubble_ = new StatusBubbleMacIgnoreMouseMoved(window, delegate_); 105 EXPECT_TRUE(bubble_); 106 107 // Turn off delays and transitions for test mode. This doesn't just speed 108 // things along, it's actually required to get StatusBubbleMac to behave 109 // synchronously, because the tests here don't know how to wait for 110 // results. This allows these tests to be much more complete with a 111 // minimal loss of coverage and without any heinous rearchitecting. 112 bubble_->immediate_ = true; 113 114 EXPECT_TRUE(bubble_->window_); // immediately creates window 115 } 116 117 virtual void TearDown() { 118 // Not using a scoped_ptr because bubble must be deleted before calling 119 // TearDown to get rid of bubble's window. 120 delete bubble_; 121 CocoaTest::TearDown(); 122 } 123 124 bool IsVisible() { 125 if (![bubble_->window_ isVisible]) 126 return false; 127 return [bubble_->window_ alphaValue] > 0.0; 128 } 129 NSString* GetText() { 130 return bubble_->status_text_; 131 } 132 NSString* GetURLText() { 133 return bubble_->url_text_; 134 } 135 NSString* GetBubbleViewText() { 136 BubbleView* bubbleView = [bubble_->window_ contentView]; 137 return [bubbleView content]; 138 } 139 StatusBubbleWindow* GetWindow() { 140 return bubble_->window_; 141 } 142 NSWindow* parent() { 143 return bubble_->parent_; 144 } 145 StatusBubbleMac::StatusBubbleState GetState() { 146 return bubble_->state_; 147 } 148 void SetState(StatusBubbleMac::StatusBubbleState state) { 149 bubble_->SetState(state); 150 } 151 std::vector<StatusBubbleMac::StatusBubbleState>* States() { 152 return [delegate_ states]; 153 } 154 StatusBubbleMac::StatusBubbleState StateAt(int index) { 155 return (*States())[index]; 156 } 157 158 bool IsPointInBubble(int x, int y) { 159 return NSPointInRect(NSMakePoint(x, y), [GetWindow() frame]); 160 } 161 162 void SetMouseLocation(int relative_x, int relative_y) { 163 // Convert to screen coordinates. 164 NSRect window_frame = [test_window() frame]; 165 int x = relative_x + window_frame.origin.x; 166 int y = relative_y + window_frame.origin.y; 167 168 ((StatusBubbleMacIgnoreMouseMoved*) 169 bubble_)->SetMouseLocationForTesting(x, y); 170 } 171 172 // Test helper for moving the fake mouse location, and checking that 173 // the bubble avoids that location. 174 // For convenience & clarity, coordinates are relative to the main window. 175 bool CheckAvoidsMouse(int relative_x, int relative_y) { 176 SetMouseLocation(relative_x, relative_y); 177 return !IsPointInBubble(relative_x, relative_y); 178 } 179 180 base::MessageLoop message_loop_; 181 base::scoped_nsobject<StatusBubbleMacTestDelegate> delegate_; 182 StatusBubbleMac* bubble_; // Strong. 183 }; 184 185 TEST_F(StatusBubbleMacTest, SetStatus) { 186 bubble_->SetStatus(base::string16()); 187 bubble_->SetStatus(UTF8ToUTF16("This is a test")); 188 EXPECT_NSEQ(@"This is a test", GetText()); 189 EXPECT_TRUE(IsVisible()); 190 191 // Set the status to the exact same thing again 192 bubble_->SetStatus(UTF8ToUTF16("This is a test")); 193 EXPECT_NSEQ(@"This is a test", GetText()); 194 195 // Hide it 196 bubble_->SetStatus(base::string16()); 197 EXPECT_FALSE(IsVisible()); 198 } 199 200 TEST_F(StatusBubbleMacTest, SetURL) { 201 bubble_->SetURL(GURL(), std::string()); 202 EXPECT_FALSE(IsVisible()); 203 bubble_->SetURL(GURL("bad url"), std::string()); 204 EXPECT_FALSE(IsVisible()); 205 bubble_->SetURL(GURL("http://"), std::string()); 206 EXPECT_TRUE(IsVisible()); 207 EXPECT_NSEQ(@"http:", GetURLText()); 208 bubble_->SetURL(GURL("about:blank"), std::string()); 209 EXPECT_TRUE(IsVisible()); 210 EXPECT_NSEQ(@"about:blank", GetURLText()); 211 bubble_->SetURL(GURL("foopy://"), std::string()); 212 EXPECT_TRUE(IsVisible()); 213 EXPECT_NSEQ(@"foopy://", GetURLText()); 214 bubble_->SetURL(GURL("http://www.cnn.com"), std::string()); 215 EXPECT_TRUE(IsVisible()); 216 EXPECT_NSEQ(@"www.cnn.com", GetURLText()); 217 } 218 219 // Test hiding bubble that's already hidden. 220 TEST_F(StatusBubbleMacTest, Hides) { 221 bubble_->SetStatus(UTF8ToUTF16("Showing")); 222 EXPECT_TRUE(IsVisible()); 223 bubble_->Hide(); 224 EXPECT_FALSE(IsVisible()); 225 bubble_->Hide(); 226 EXPECT_FALSE(IsVisible()); 227 } 228 229 // Test the "main"/"backup" behavior in StatusBubbleMac::SetText(). 230 TEST_F(StatusBubbleMacTest, SetStatusAndURL) { 231 EXPECT_FALSE(IsVisible()); 232 bubble_->SetStatus(UTF8ToUTF16("Status")); 233 EXPECT_TRUE(IsVisible()); 234 EXPECT_NSEQ(@"Status", GetBubbleViewText()); 235 bubble_->SetURL(GURL("http://www.nytimes.com"), std::string()); 236 EXPECT_TRUE(IsVisible()); 237 EXPECT_NSEQ(@"www.nytimes.com", GetBubbleViewText()); 238 bubble_->SetURL(GURL(), std::string()); 239 EXPECT_TRUE(IsVisible()); 240 EXPECT_NSEQ(@"Status", GetBubbleViewText()); 241 bubble_->SetStatus(base::string16()); 242 EXPECT_FALSE(IsVisible()); 243 bubble_->SetURL(GURL("http://www.nytimes.com"), std::string()); 244 EXPECT_TRUE(IsVisible()); 245 EXPECT_NSEQ(@"www.nytimes.com", GetBubbleViewText()); 246 bubble_->SetStatus(UTF8ToUTF16("Status")); 247 EXPECT_TRUE(IsVisible()); 248 EXPECT_NSEQ(@"Status", GetBubbleViewText()); 249 bubble_->SetStatus(base::string16()); 250 EXPECT_TRUE(IsVisible()); 251 EXPECT_NSEQ(@"www.nytimes.com", GetBubbleViewText()); 252 bubble_->SetURL(GURL(), std::string()); 253 EXPECT_FALSE(IsVisible()); 254 } 255 256 // Test that the status bubble goes through the correct delay and fade states. 257 // The delay and fade duration are simulated and not actually experienced 258 // during the test because StatusBubbleMacTest sets immediate_ mode. 259 TEST_F(StatusBubbleMacTest, StateTransitions) { 260 // First, some sanity 261 262 EXPECT_FALSE(IsVisible()); 263 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 264 265 States()->clear(); 266 EXPECT_TRUE(States()->empty()); 267 268 bubble_->SetStatus(base::string16()); 269 EXPECT_FALSE(IsVisible()); 270 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 271 EXPECT_TRUE(States()->empty()); // no change from initial kBubbleHidden state 272 273 // Next, a few ordinary cases 274 275 // Test StartShowing from kBubbleHidden 276 bubble_->SetStatus(UTF8ToUTF16("Status")); 277 EXPECT_TRUE(IsVisible()); 278 // Check GetState before checking States to make sure that all state 279 // transitions have been flushed to States. 280 EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState()); 281 EXPECT_EQ(3u, States()->size()); 282 EXPECT_EQ(StatusBubbleMac::kBubbleShowingTimer, StateAt(0)); 283 EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(1)); 284 EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(2)); 285 286 // Test StartShowing from kBubbleShown with the same message 287 States()->clear(); 288 bubble_->SetStatus(UTF8ToUTF16("Status")); 289 EXPECT_TRUE(IsVisible()); 290 EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState()); 291 EXPECT_TRUE(States()->empty()); 292 293 // Test StartShowing from kBubbleShown with a different message 294 bubble_->SetStatus(UTF8ToUTF16("New Status")); 295 EXPECT_TRUE(IsVisible()); 296 EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState()); 297 EXPECT_TRUE(States()->empty()); 298 299 // Test StartHiding from kBubbleShown 300 bubble_->SetStatus(base::string16()); 301 EXPECT_FALSE(IsVisible()); 302 // Check GetState before checking States to make sure that all state 303 // transitions have been flushed to States. 304 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 305 EXPECT_EQ(3u, States()->size()); 306 EXPECT_EQ(StatusBubbleMac::kBubbleHidingTimer, StateAt(0)); 307 EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(1)); 308 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(2)); 309 310 // Test StartHiding from kBubbleHidden 311 States()->clear(); 312 bubble_->SetStatus(base::string16()); 313 EXPECT_FALSE(IsVisible()); 314 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 315 EXPECT_TRUE(States()->empty()); 316 317 // Now, the edge cases 318 319 // Test StartShowing from kBubbleShowingTimer 320 bubble_->SetStatus(UTF8ToUTF16("Status")); 321 SetState(StatusBubbleMac::kBubbleShowingTimer); 322 [GetWindow() setAlphaValue:0.0]; 323 States()->clear(); 324 EXPECT_TRUE(States()->empty()); 325 bubble_->SetStatus(UTF8ToUTF16("Status")); 326 EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState()); 327 EXPECT_EQ(2u, States()->size()); 328 EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(0)); 329 EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(1)); 330 331 // Test StartShowing from kBubbleShowingFadeIn 332 bubble_->SetStatus(UTF8ToUTF16("Status")); 333 SetState(StatusBubbleMac::kBubbleShowingFadeIn); 334 [GetWindow() setAlphaValue:0.5]; 335 States()->clear(); 336 EXPECT_TRUE(States()->empty()); 337 bubble_->SetStatus(UTF8ToUTF16("Status")); 338 // The actual state values can't be tested in immediate_ mode because 339 // the window wasn't actually fading in. Without immediate_ mode, 340 // expect kBubbleShown. 341 bubble_->SetStatus(base::string16()); // Go back to a deterministic state. 342 343 // Test StartShowing from kBubbleHidingTimer 344 bubble_->SetStatus(base::string16()); 345 SetState(StatusBubbleMac::kBubbleHidingTimer); 346 [GetWindow() setAlphaValue:1.0]; 347 States()->clear(); 348 EXPECT_TRUE(States()->empty()); 349 bubble_->SetStatus(UTF8ToUTF16("Status")); 350 EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState()); 351 EXPECT_EQ(1u, States()->size()); 352 EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(0)); 353 354 // Test StartShowing from kBubbleHidingFadeOut 355 bubble_->SetStatus(base::string16()); 356 SetState(StatusBubbleMac::kBubbleHidingFadeOut); 357 [GetWindow() setAlphaValue:0.5]; 358 States()->clear(); 359 EXPECT_TRUE(States()->empty()); 360 bubble_->SetStatus(UTF8ToUTF16("Status")); 361 EXPECT_EQ(StatusBubbleMac::kBubbleShown, GetState()); 362 EXPECT_EQ(2u, States()->size()); 363 EXPECT_EQ(StatusBubbleMac::kBubbleShowingFadeIn, StateAt(0)); 364 EXPECT_EQ(StatusBubbleMac::kBubbleShown, StateAt(1)); 365 366 // Test StartHiding from kBubbleShowingTimer 367 bubble_->SetStatus(UTF8ToUTF16("Status")); 368 SetState(StatusBubbleMac::kBubbleShowingTimer); 369 [GetWindow() setAlphaValue:0.0]; 370 States()->clear(); 371 EXPECT_TRUE(States()->empty()); 372 bubble_->SetStatus(base::string16()); 373 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 374 EXPECT_EQ(1u, States()->size()); 375 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0)); 376 377 // Test StartHiding from kBubbleShowingFadeIn 378 bubble_->SetStatus(UTF8ToUTF16("Status")); 379 SetState(StatusBubbleMac::kBubbleShowingFadeIn); 380 [GetWindow() setAlphaValue:0.5]; 381 States()->clear(); 382 EXPECT_TRUE(States()->empty()); 383 bubble_->SetStatus(base::string16()); 384 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 385 EXPECT_EQ(2u, States()->size()); 386 EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(0)); 387 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(1)); 388 389 // Test StartHiding from kBubbleHidingTimer 390 bubble_->SetStatus(base::string16()); 391 SetState(StatusBubbleMac::kBubbleHidingTimer); 392 [GetWindow() setAlphaValue:1.0]; 393 States()->clear(); 394 EXPECT_TRUE(States()->empty()); 395 bubble_->SetStatus(base::string16()); 396 // The actual state values can't be tested in immediate_ mode because 397 // the timer wasn't actually running. Without immediate_ mode, expect 398 // kBubbleHidingFadeOut and kBubbleHidden. 399 // Go back to a deterministic state. 400 bubble_->SetStatus(UTF8ToUTF16("Status")); 401 402 // Test StartHiding from kBubbleHidingFadeOut 403 bubble_->SetStatus(base::string16()); 404 SetState(StatusBubbleMac::kBubbleHidingFadeOut); 405 [GetWindow() setAlphaValue:0.5]; 406 States()->clear(); 407 EXPECT_TRUE(States()->empty()); 408 bubble_->SetStatus(base::string16()); 409 // The actual state values can't be tested in immediate_ mode because 410 // the window wasn't actually fading out. Without immediate_ mode, expect 411 // kBubbleHidden. 412 // Go back to a deterministic state. 413 bubble_->SetStatus(UTF8ToUTF16("Status")); 414 415 // Test Hide from kBubbleHidden 416 bubble_->SetStatus(base::string16()); 417 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 418 States()->clear(); 419 EXPECT_TRUE(States()->empty()); 420 bubble_->Hide(); 421 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 422 EXPECT_TRUE(States()->empty()); 423 424 // Test Hide from kBubbleShowingTimer 425 bubble_->SetStatus(UTF8ToUTF16("Status")); 426 SetState(StatusBubbleMac::kBubbleShowingTimer); 427 [GetWindow() setAlphaValue:0.0]; 428 States()->clear(); 429 EXPECT_TRUE(States()->empty()); 430 bubble_->Hide(); 431 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 432 EXPECT_EQ(1u, States()->size()); 433 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0)); 434 435 // Test Hide from kBubbleShowingFadeIn 436 bubble_->SetStatus(UTF8ToUTF16("Status")); 437 SetState(StatusBubbleMac::kBubbleShowingFadeIn); 438 [GetWindow() setAlphaValue:0.5]; 439 States()->clear(); 440 EXPECT_TRUE(States()->empty()); 441 bubble_->Hide(); 442 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 443 EXPECT_EQ(2u, States()->size()); 444 EXPECT_EQ(StatusBubbleMac::kBubbleHidingFadeOut, StateAt(0)); 445 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(1)); 446 447 // Test Hide from kBubbleShown 448 bubble_->SetStatus(UTF8ToUTF16("Status")); 449 States()->clear(); 450 EXPECT_TRUE(States()->empty()); 451 bubble_->Hide(); 452 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 453 EXPECT_EQ(1u, States()->size()); 454 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0)); 455 456 // Test Hide from kBubbleHidingTimer 457 bubble_->SetStatus(UTF8ToUTF16("Status")); 458 SetState(StatusBubbleMac::kBubbleHidingTimer); 459 States()->clear(); 460 EXPECT_TRUE(States()->empty()); 461 bubble_->Hide(); 462 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 463 EXPECT_EQ(1u, States()->size()); 464 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0)); 465 466 // Test Hide from kBubbleHidingFadeOut 467 bubble_->SetStatus(UTF8ToUTF16("Status")); 468 SetState(StatusBubbleMac::kBubbleHidingFadeOut); 469 [GetWindow() setAlphaValue:0.5]; 470 States()->clear(); 471 EXPECT_TRUE(States()->empty()); 472 bubble_->Hide(); 473 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, GetState()); 474 EXPECT_EQ(1u, States()->size()); 475 EXPECT_EQ(StatusBubbleMac::kBubbleHidden, StateAt(0)); 476 } 477 478 TEST_F(StatusBubbleMacTest, Delete) { 479 NSWindow* window = test_window(); 480 // Create and delete immediately. 481 StatusBubbleMac* bubble = new StatusBubbleMac(window, nil); 482 delete bubble; 483 484 // Create then delete while visible. 485 bubble = new StatusBubbleMac(window, nil); 486 bubble->SetStatus(UTF8ToUTF16("showing")); 487 delete bubble; 488 } 489 490 TEST_F(StatusBubbleMacTest, UpdateSizeAndPosition) { 491 // Test |UpdateSizeAndPosition()| when status bubble does not exist (shouldn't 492 // crash; shouldn't create window). 493 EXPECT_TRUE(GetWindow()); 494 bubble_->UpdateSizeAndPosition(); 495 EXPECT_TRUE(GetWindow()); 496 497 // Create a status bubble (with contents) and call resize (without actually 498 // resizing); the frame size shouldn't change. 499 bubble_->SetStatus(UTF8ToUTF16("UpdateSizeAndPosition test")); 500 ASSERT_TRUE(GetWindow()); 501 NSRect rect_before = [GetWindow() frame]; 502 bubble_->UpdateSizeAndPosition(); 503 NSRect rect_after = [GetWindow() frame]; 504 EXPECT_TRUE(NSEqualRects(rect_before, rect_after)); 505 506 // Move the window and call resize; only the origin should change. 507 NSWindow* window = test_window(); 508 ASSERT_TRUE(window); 509 NSRect frame = [window frame]; 510 rect_before = [GetWindow() frame]; 511 frame.origin.x += 10.0; // (fairly arbitrary nonzero value) 512 frame.origin.y += 10.0; // (fairly arbitrary nonzero value) 513 [window setFrame:frame display:YES]; 514 bubble_->UpdateSizeAndPosition(); 515 rect_after = [GetWindow() frame]; 516 EXPECT_NE(rect_before.origin.x, rect_after.origin.x); 517 EXPECT_NE(rect_before.origin.y, rect_after.origin.y); 518 EXPECT_EQ(rect_before.size.width, rect_after.size.width); 519 EXPECT_EQ(rect_before.size.height, rect_after.size.height); 520 521 // Resize the window (without moving). The origin shouldn't change. The width 522 // should change (in the current implementation), but not the height. 523 frame = [window frame]; 524 rect_before = [GetWindow() frame]; 525 frame.size.width += 50.0; // (fairly arbitrary nonzero value) 526 frame.size.height += 50.0; // (fairly arbitrary nonzero value) 527 [window setFrame:frame display:YES]; 528 bubble_->UpdateSizeAndPosition(); 529 rect_after = [GetWindow() frame]; 530 EXPECT_EQ(rect_before.origin.x, rect_after.origin.x); 531 EXPECT_EQ(rect_before.origin.y, rect_after.origin.y); 532 EXPECT_NE(rect_before.size.width, rect_after.size.width); 533 EXPECT_EQ(rect_before.size.height, rect_after.size.height); 534 } 535 536 TEST_F(StatusBubbleMacTest, MovingWindowUpdatesPosition) { 537 NSWindow* window = test_window(); 538 539 // Show the bubble and make sure it has the same origin as |window|. 540 bubble_->SetStatus(UTF8ToUTF16("Showing")); 541 StatusBubbleWindow* child = GetWindow(); 542 EXPECT_TRUE(NSEqualPoints([window frame].origin, [child frame].origin)); 543 544 // Hide the bubble, move the window, and show it again. 545 bubble_->Hide(); 546 NSRect frame = [window frame]; 547 frame.origin.x += 50; 548 [window setFrame:frame display:YES]; 549 bubble_->SetStatus(UTF8ToUTF16("Reshowing")); 550 551 // The bubble should reattach in the correct location. 552 child = GetWindow(); 553 EXPECT_TRUE(NSEqualPoints([window frame].origin, [child frame].origin)); 554 } 555 556 TEST_F(StatusBubbleMacTest, StatuBubbleRespectsBaseFrameLimits) { 557 NSWindow* window = test_window(); 558 559 // Show the bubble and make sure it has the same origin as |window|. 560 bubble_->SetStatus(UTF8ToUTF16("Showing")); 561 StatusBubbleWindow* child = GetWindow(); 562 EXPECT_TRUE(NSEqualPoints([window frame].origin, [child frame].origin)); 563 564 // Hide the bubble, change base frame offset, and show it again. 565 bubble_->Hide(); 566 567 NSPoint baseFrameOffset = NSMakePoint(0, [window frame].size.height / 3); 568 EXPECT_GT(baseFrameOffset.y, 0); 569 [delegate_ forceBaseFrameOffset:baseFrameOffset]; 570 571 bubble_->SetStatus(UTF8ToUTF16("Reshowing")); 572 573 // The bubble should reattach in the correct location. 574 child = GetWindow(); 575 NSPoint expectedOrigin = [window frame].origin; 576 expectedOrigin.x += baseFrameOffset.x; 577 expectedOrigin.y += baseFrameOffset.y; 578 EXPECT_TRUE(NSEqualPoints(expectedOrigin, [child frame].origin)); 579 } 580 581 TEST_F(StatusBubbleMacTest, ExpandBubble) { 582 NSWindow* window = test_window(); 583 584 // The system font changes between OSX 10.9 and OSX 10.10. Use the system 585 // font from OSX 10.9 for this test. 586 id mockContentView = 587 [OCMockObject partialMockForObject:[GetWindow() contentView]]; 588 [[[mockContentView stub] 589 andReturn:[NSFont fontWithName:@"Lucida Grande" size:11]] font]; 590 591 ASSERT_TRUE(window); 592 NSRect window_frame = [window frame]; 593 window_frame.size.width = 600.0; 594 [window setFrame:window_frame display:YES]; 595 596 // Check basic expansion 597 bubble_->SetStatus(UTF8ToUTF16("Showing")); 598 EXPECT_TRUE(IsVisible()); 599 bubble_->SetURL(GURL("http://www.battersbox.com/peter_paul_and_mary.html"), 600 std::string()); 601 EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]); 602 bubble_->ExpandBubble(); 603 EXPECT_TRUE(IsVisible()); 604 EXPECT_NSEQ(@"www.battersbox.com/peter_paul_and_mary.html", GetURLText()); 605 bubble_->Hide(); 606 607 // Make sure bubble resets after hide. 608 bubble_->SetStatus(UTF8ToUTF16("Showing")); 609 bubble_->SetURL(GURL("http://www.snickersnee.com/pioneer_fishstix.html"), 610 std::string()); 611 EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]); 612 // ...and that it expands again properly. 613 bubble_->ExpandBubble(); 614 EXPECT_NSEQ(@"www.snickersnee.com/pioneer_fishstix.html", GetURLText()); 615 // ...again, again! 616 bubble_->SetURL(GURL("http://www.battersbox.com/peter_paul_and_mary.html"), 617 std::string()); 618 bubble_->ExpandBubble(); 619 EXPECT_NSEQ(@"www.battersbox.com/peter_paul_and_mary.html", GetURLText()); 620 bubble_->Hide(); 621 622 window_frame = [window frame]; 623 window_frame.size.width = 300.0; 624 [window setFrame:window_frame display:YES]; 625 626 // Very long URL's will be cut off even in the expanded state. 627 bubble_->SetStatus(UTF8ToUTF16("Showing")); 628 const char veryLongUrl[] = 629 "http://www.diewahrscheinlichlaengstepralinederwelt.com/duuuuplo.html"; 630 bubble_->SetURL(GURL(veryLongUrl), std::string()); 631 EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]); 632 bubble_->ExpandBubble(); 633 EXPECT_TRUE([GetURLText() hasSuffix:@"\u2026"]); 634 } 635 636 TEST_F(StatusBubbleMacTest, BubbleAvoidsMouse) { 637 NSWindow* window = test_window(); 638 639 // All coordinates here are relative to the window origin. 640 641 // Initially, the bubble should appear in the bottom left. 642 bubble_->SetStatus(UTF8ToUTF16("Showing")); 643 EXPECT_TRUE(IsPointInBubble(0, 0)); 644 bubble_->Hide(); 645 646 // Check that the bubble doesn't appear in the left corner if the 647 // mouse is currently located there. 648 SetMouseLocation(0, 0); 649 bubble_->SetStatus(UTF8ToUTF16("Showing")); 650 EXPECT_FALSE(IsPointInBubble(0, 0)); 651 652 // Leave the bubble visible, and try moving the mouse around. 653 int smallValue = NSHeight([GetWindow() frame]) / 2; 654 EXPECT_TRUE(CheckAvoidsMouse(0, 0)); 655 EXPECT_TRUE(CheckAvoidsMouse(smallValue, 0)); 656 EXPECT_TRUE(CheckAvoidsMouse(0, smallValue)); 657 EXPECT_TRUE(CheckAvoidsMouse(smallValue, smallValue)); 658 659 // Simulate moving the mouse down from the top of the window. 660 for (int y = NSHeight([window frame]); y >= 0; y -= smallValue) { 661 ASSERT_TRUE(CheckAvoidsMouse(smallValue, y)); 662 } 663 664 // Simulate moving the mouse from left to right. 665 int windowWidth = NSWidth([window frame]); 666 for (int x = 0; x < windowWidth; x += smallValue) { 667 ASSERT_TRUE(CheckAvoidsMouse(x, smallValue)); 668 } 669 670 // Simulate moving the mouse from right to left. 671 for (int x = windowWidth; x >= 0; x -= smallValue) { 672 ASSERT_TRUE(CheckAvoidsMouse(x, smallValue)); 673 } 674 } 675