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
21using 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.
68class 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
95class 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
185TEST_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
200TEST_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.
220TEST_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().
230TEST_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.
259TEST_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
478TEST_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
490TEST_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
536TEST_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
556TEST_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
581TEST_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
636TEST_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