Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / profiles / avatar_menu_bubble_controller_unittest.mm
blob1ededede71e0d4fd215c8ef3dc86bae7c588eda1
1 // Copyright 2014 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.
5 #import "chrome/browser/ui/cocoa/profiles/avatar_menu_bubble_controller.h"
7 #include "base/command_line.h"
8 #include "base/mac/scoped_nsobject.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/message_loop/message_pump_mac.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/profiles/avatar_menu.h"
13 #include "chrome/browser/profiles/avatar_menu_observer.h"
14 #include "chrome/browser/profiles/profile_info_cache.h"
15 #import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
16 #include "chrome/test/base/testing_browser_process.h"
17 #include "chrome/test/base/testing_profile_manager.h"
18 #include "components/signin/core/common/profile_management_switches.h"
19 #include "components/syncable_prefs/pref_service_syncable.h"
20 #include "content/public/test/test_browser_thread_bundle.h"
21 #include "testing/gtest_mac.h"
22 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
23 #include "ui/events/test/cocoa_test_event_utils.h"
25 class AvatarMenuBubbleControllerTest : public CocoaTest {
26  public:
27   AvatarMenuBubbleControllerTest()
28       : manager_(TestingBrowserProcess::GetGlobal()) {
29   }
31   void SetUp() override {
32     switches::DisableNewAvatarMenuForTesting(
33         base::CommandLine::ForCurrentProcess());
35     CocoaTest::SetUp();
36     ASSERT_TRUE(manager_.SetUp());
38     manager_.CreateTestingProfile(
39         "test1", scoped_ptr<syncable_prefs::PrefServiceSyncable>(),
40         base::ASCIIToUTF16("Test 1"), 1, std::string(),
41         TestingProfile::TestingFactories());
42     manager_.CreateTestingProfile(
43         "test2", scoped_ptr<syncable_prefs::PrefServiceSyncable>(),
44         base::ASCIIToUTF16("Test 2"), 0, std::string(),
45         TestingProfile::TestingFactories());
47     menu_ = new AvatarMenu(manager_.profile_info_cache(), NULL, NULL);
48     menu_->RebuildMenu();
50     NSRect frame = [test_window() frame];
51     NSPoint point = NSMakePoint(NSMidX(frame), NSMidY(frame));
52     controller_ =
53         [[AvatarMenuBubbleController alloc] initWithMenu:menu()
54                                              parentWindow:test_window()
55                                                anchoredAt:point];
56   }
58   TestingProfileManager* manager() { return &manager_; }
59   AvatarMenuBubbleController* controller() { return controller_; }
60   AvatarMenu* menu() { return menu_; }
62   AvatarMenuItemController* GetHighlightedItem() {
63     for (AvatarMenuItemController* item in [controller() items]) {
64       if ([item isHighlighted])
65         return item;
66     }
67     return nil;
68   }
70  private:
71   content::TestBrowserThreadBundle thread_bundle_;
72   TestingProfileManager manager_;
74   // Weak; releases self.
75   AvatarMenuBubbleController* controller_;
77   // Weak; owned by |controller_|.
78   AvatarMenu* menu_;
81 TEST_F(AvatarMenuBubbleControllerTest, InitialLayout) {
82   [controller() showWindow:nil];
84   // Two profiles means two item views and the new button with separator.
85   NSView* contents = [[controller() window] contentView];
86   EXPECT_EQ(4U, [[contents subviews] count]);
88   // Loop over the items and match the viewController views to subviews.
89   NSMutableArray* subviews =
90       [NSMutableArray arrayWithArray:[contents subviews]];
91   for (AvatarMenuItemController* viewController in [controller() items]) {
92     for (NSView* subview in subviews) {
93       if ([viewController view] == subview) {
94         [subviews removeObject:subview];
95         break;
96       }
97     }
98   }
100   // The one remaining subview should be the new user button.
101   EXPECT_EQ(2U, [subviews count]);
103   BOOL hasButton = NO;
104   BOOL hasSeparator = NO;
105   for (NSView* subview in subviews) {
106     if ([subview isKindOfClass:[NSButton class]]) {
107       EXPECT_FALSE(hasButton);
108       hasButton = YES;
110       NSButton* button = static_cast<NSButton*>(subview);
111       EXPECT_EQ(@selector(newProfile:), [button action]);
112       EXPECT_EQ(controller(), [button target]);
113       EXPECT_TRUE([[button cell] isKindOfClass:[HyperlinkButtonCell class]]);
114     } else if ([subview isKindOfClass:[NSBox class]]) {
115       EXPECT_FALSE(hasSeparator);
116       hasSeparator = YES;
117     } else {
118       EXPECT_FALSE(subview) << "Unexpected subview: "
119                             << [[subview description] UTF8String];
120     }
121   }
123   [controller() close];
126 TEST_F(AvatarMenuBubbleControllerTest, PerformLayout) {
127   [controller() showWindow:nil];
129   NSView* contents = [[controller() window] contentView];
130   EXPECT_EQ(4U, [[contents subviews] count]);
132   base::scoped_nsobject<NSMutableArray> oldItems([[controller() items] copy]);
134   // Now create a new profile and notify the delegate.
135   manager()->CreateTestingProfile(
136       "test3", scoped_ptr<syncable_prefs::PrefServiceSyncable>(),
137       base::ASCIIToUTF16("Test 3"), 0, std::string(),
138       TestingProfile::TestingFactories());
140   // Testing the bridge is not worth the effort...
141   [controller() performLayout];
143   EXPECT_EQ(5U, [[contents subviews] count]);
145   // Make sure that none of the old items exit.
146   NSArray* newItems = [controller() items];
147   for (AvatarMenuItemController* oldVC in oldItems.get()) {
148     EXPECT_FALSE([newItems containsObject:oldVC]);
149     EXPECT_FALSE([[contents subviews] containsObject:[oldVC view]]);
150   }
152   [controller() close];
155 // This subclass is used to inject a delegate into the hide/show edit link
156 // animation.
157 @interface TestingAvatarMenuItemController : AvatarMenuItemController
158                                                  <NSAnimationDelegate> {
159  @private
160   scoped_ptr<base::MessagePumpNSRunLoop> pump_;
162 // After calling |-highlightForEventType:| an animation will possibly be
163 // started. Since the animation is non-blocking, the run loop will need to be
164 // spun (via the MessagePump) until the animation has finished.
165 - (void)runMessagePump;
166 @end
168 @implementation TestingAvatarMenuItemController
169 - (void)runMessagePump {
170   if (!pump_)
171     pump_.reset(new base::MessagePumpNSRunLoop);
172   pump_->Run(NULL);
175 - (void)willStartAnimation:(NSAnimation*)anim {
176   [anim setDelegate:self];
179 - (void)animationDidEnd:(NSAnimation*)anim {
180   [super animationDidEnd:anim];
181   pump_->Quit();
184 - (void)animationDidStop:(NSAnimation*)anim {
185   [super animationDidStop:anim];
186   FAIL() << "Animation stopped before it completed its run";
187   pump_->Quit();
190 - (void)sendHighlightMessageForMouseExited {
191   [self highlightForEventType:NSMouseExited];
192   // Quit the pump because the animation was cancelled before it even ran.
193   pump_->Quit();
195 @end
197 TEST_F(AvatarMenuBubbleControllerTest, HighlightForEventType) {
198   base::scoped_nsobject<TestingAvatarMenuItemController> item(
199       [[TestingAvatarMenuItemController alloc] initWithMenuIndex:0
200                                                    menuController:nil]);
201   // Test non-active states first.
202   [[item activeView] setHidden:YES];
204   NSView* editButton = [item editButton];
205   NSView* emailField = [item emailField];
207   // The edit link remains hidden.
208   [item setIsHighlighted:YES];
209   EXPECT_TRUE(editButton.isHidden);
210   EXPECT_FALSE(emailField.isHidden);
212   [item setIsHighlighted:NO];
213   EXPECT_TRUE(editButton.isHidden);
214   EXPECT_FALSE(emailField.isHidden);
216   // Make the item "active" and re-test.
217   [[item activeView] setHidden:NO];
219   [item setIsHighlighted:YES];
220   [item runMessagePump];
222   EXPECT_FALSE(editButton.isHidden);
223   EXPECT_TRUE(emailField.isHidden);
225   [item setIsHighlighted:NO];
226   [item runMessagePump];
228   EXPECT_TRUE(editButton.isHidden);
229   EXPECT_FALSE(emailField.isHidden);
231   // Now mouse over and out quickly, as if scrubbing through the menu, to test
232   // the hover dwell delay.
233   [item highlightForEventType:NSMouseEntered];
234   [item performSelector:@selector(sendHighlightMessageForMouseExited)
235              withObject:nil
236              afterDelay:0];
237   [item runMessagePump];
239   EXPECT_TRUE(editButton.isHidden);
240   EXPECT_FALSE(emailField.isHidden);
243 TEST_F(AvatarMenuBubbleControllerTest, DownArrow) {
244   EXPECT_NSEQ(nil, GetHighlightedItem());
246   NSEvent* event =
247       cocoa_test_event_utils::KeyEventWithCharacter(NSDownArrowFunctionKey);
248   // Going down with no item selected should start the selection at the first
249   // item.
250   [controller() keyDown:event];
251   EXPECT_EQ([[controller() items] objectAtIndex:1], GetHighlightedItem());
253   [controller() keyDown:event];
254   EXPECT_EQ([[controller() items] objectAtIndex:0], GetHighlightedItem());
256   // There are no more items now so going down should stay at the last item.
257   [controller() keyDown:event];
258   EXPECT_EQ([[controller() items] objectAtIndex:0], GetHighlightedItem());
261 TEST_F(AvatarMenuBubbleControllerTest, UpArrow) {
262   EXPECT_NSEQ(nil, GetHighlightedItem());
264   NSEvent* event =
265       cocoa_test_event_utils::KeyEventWithCharacter(NSUpArrowFunctionKey);
266   // Going up with no item selected should start the selection at the last
267   // item.
268   [controller() keyDown:event];
269   EXPECT_EQ([[controller() items] objectAtIndex:0], GetHighlightedItem());
271   [controller() keyDown:event];
272   EXPECT_EQ([[controller() items] objectAtIndex:1], GetHighlightedItem());
274   // There are no more items now so going up should stay at the first item.
275   [controller() keyDown:event];
276   EXPECT_EQ([[controller() items] objectAtIndex:1], GetHighlightedItem());