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/mac/scoped_nsobject.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/message_loop/message_pump_mac.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/prefs/pref_service_syncable.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 "testing/gtest_mac.h"
19 #import "ui/base/cocoa/controls/hyperlink_button_cell.h"
20 #include "ui/events/test/cocoa_test_event_utils.h"
22 class AvatarMenuBubbleControllerTest : public CocoaTest {
24 AvatarMenuBubbleControllerTest()
25 : manager_(TestingBrowserProcess::GetGlobal()) {
28 virtual void SetUp() {
30 ASSERT_TRUE(manager_.SetUp());
32 manager_.CreateTestingProfile("test1", scoped_ptr<PrefServiceSyncable>(),
33 base::ASCIIToUTF16("Test 1"), 1,
35 TestingProfile::TestingFactories());
36 manager_.CreateTestingProfile("test2", scoped_ptr<PrefServiceSyncable>(),
37 base::ASCIIToUTF16("Test 2"), 0,
39 TestingProfile::TestingFactories());
41 menu_ = new AvatarMenu(manager_.profile_info_cache(), NULL, NULL);
44 NSRect frame = [test_window() frame];
45 NSPoint point = NSMakePoint(NSMidX(frame), NSMidY(frame));
47 [[AvatarMenuBubbleController alloc] initWithMenu:menu()
48 parentWindow:test_window()
52 TestingProfileManager* manager() { return &manager_; }
53 AvatarMenuBubbleController* controller() { return controller_; }
54 AvatarMenu* menu() { return menu_; }
56 AvatarMenuItemController* GetHighlightedItem() {
57 for (AvatarMenuItemController* item in [controller() items]) {
58 if ([item isHighlighted])
65 TestingProfileManager manager_;
67 // Weak; releases self.
68 AvatarMenuBubbleController* controller_;
70 // Weak; owned by |controller_|.
74 TEST_F(AvatarMenuBubbleControllerTest, InitialLayout) {
75 [controller() showWindow:nil];
77 // Two profiles means two item views and the new button with separator.
78 NSView* contents = [[controller() window] contentView];
79 EXPECT_EQ(4U, [[contents subviews] count]);
81 // Loop over the itmes and match the viewController views to subviews.
82 NSMutableArray* subviews =
83 [NSMutableArray arrayWithArray:[contents subviews]];
84 for (AvatarMenuItemController* viewController in [controller() items]) {
85 for (NSView* subview in subviews) {
86 if ([viewController view] == subview) {
87 [subviews removeObject:subview];
93 // The one remaining subview should be the new user button.
94 EXPECT_EQ(2U, [subviews count]);
97 BOOL hasSeparator = NO;
98 for (NSView* subview in subviews) {
99 if ([subview isKindOfClass:[NSButton class]]) {
100 EXPECT_FALSE(hasButton);
103 NSButton* button = static_cast<NSButton*>(subview);
104 EXPECT_EQ(@selector(newProfile:), [button action]);
105 EXPECT_EQ(controller(), [button target]);
106 EXPECT_TRUE([[button cell] isKindOfClass:[HyperlinkButtonCell class]]);
107 } else if ([subview isKindOfClass:[NSBox class]]) {
108 EXPECT_FALSE(hasSeparator);
111 EXPECT_FALSE(subview) << "Unexpected subview: "
112 << [[subview description] UTF8String];
116 [controller() close];
119 TEST_F(AvatarMenuBubbleControllerTest, PerformLayout) {
120 [controller() showWindow:nil];
122 NSView* contents = [[controller() window] contentView];
123 EXPECT_EQ(4U, [[contents subviews] count]);
125 base::scoped_nsobject<NSMutableArray> oldItems([[controller() items] copy]);
127 // Now create a new profile and notify the delegate.
128 manager()->CreateTestingProfile("test3", scoped_ptr<PrefServiceSyncable>(),
129 base::ASCIIToUTF16("Test 3"), 0,
131 TestingProfile::TestingFactories());
133 // Testing the bridge is not worth the effort...
134 [controller() performLayout];
136 EXPECT_EQ(5U, [[contents subviews] count]);
138 // Make sure that none of the old items exit.
139 NSArray* newItems = [controller() items];
140 for (AvatarMenuItemController* oldVC in oldItems.get()) {
141 EXPECT_FALSE([newItems containsObject:oldVC]);
142 EXPECT_FALSE([[contents subviews] containsObject:[oldVC view]]);
145 [controller() close];
148 // This subclass is used to inject a delegate into the hide/show edit link
150 @interface TestingAvatarMenuItemController : AvatarMenuItemController
151 <NSAnimationDelegate> {
153 scoped_ptr<base::MessagePumpNSRunLoop> pump_;
155 // After calling |-highlightForEventType:| an animation will possibly be
156 // started. Since the animation is non-blocking, the run loop will need to be
157 // spun (via the MessagePump) until the animation has finished.
158 - (void)runMessagePump;
161 @implementation TestingAvatarMenuItemController
162 - (void)runMessagePump {
164 pump_.reset(new base::MessagePumpNSRunLoop);
168 - (void)willStartAnimation:(NSAnimation*)anim {
169 [anim setDelegate:self];
172 - (void)animationDidEnd:(NSAnimation*)anim {
173 [super animationDidEnd:anim];
177 - (void)animationDidStop:(NSAnimation*)anim {
178 [super animationDidStop:anim];
179 FAIL() << "Animation stopped before it completed its run";
183 - (void)sendHighlightMessageForMouseExited {
184 [self highlightForEventType:NSMouseExited];
185 // Quit the pump because the animation was cancelled before it even ran.
190 TEST_F(AvatarMenuBubbleControllerTest, HighlightForEventType) {
191 base::scoped_nsobject<TestingAvatarMenuItemController> item(
192 [[TestingAvatarMenuItemController alloc] initWithMenuIndex:0
193 menuController:nil]);
194 // Test non-active states first.
195 [[item activeView] setHidden:YES];
197 NSView* editButton = [item editButton];
198 NSView* emailField = [item emailField];
200 // The edit link remains hidden.
201 [item setIsHighlighted:YES];
202 EXPECT_TRUE(editButton.isHidden);
203 EXPECT_FALSE(emailField.isHidden);
205 [item setIsHighlighted:NO];
206 EXPECT_TRUE(editButton.isHidden);
207 EXPECT_FALSE(emailField.isHidden);
209 // Make the item "active" and re-test.
210 [[item activeView] setHidden:NO];
212 [item setIsHighlighted:YES];
213 [item runMessagePump];
215 EXPECT_FALSE(editButton.isHidden);
216 EXPECT_TRUE(emailField.isHidden);
218 [item setIsHighlighted:NO];
219 [item runMessagePump];
221 EXPECT_TRUE(editButton.isHidden);
222 EXPECT_FALSE(emailField.isHidden);
224 // Now mouse over and out quickly, as if scrubbing through the menu, to test
225 // the hover dwell delay.
226 [item highlightForEventType:NSMouseEntered];
227 [item performSelector:@selector(sendHighlightMessageForMouseExited)
230 [item runMessagePump];
232 EXPECT_TRUE(editButton.isHidden);
233 EXPECT_FALSE(emailField.isHidden);
236 TEST_F(AvatarMenuBubbleControllerTest, DownArrow) {
237 EXPECT_NSEQ(nil, GetHighlightedItem());
240 cocoa_test_event_utils::KeyEventWithCharacter(NSDownArrowFunctionKey);
241 // Going down with no item selected should start the selection at the first
243 [controller() keyDown:event];
244 EXPECT_EQ([[controller() items] objectAtIndex:1], GetHighlightedItem());
246 [controller() keyDown:event];
247 EXPECT_EQ([[controller() items] objectAtIndex:0], GetHighlightedItem());
249 // There are no more items now so going down should stay at the last item.
250 [controller() keyDown:event];
251 EXPECT_EQ([[controller() items] objectAtIndex:0], GetHighlightedItem());
254 TEST_F(AvatarMenuBubbleControllerTest, UpArrow) {
255 EXPECT_NSEQ(nil, GetHighlightedItem());
258 cocoa_test_event_utils::KeyEventWithCharacter(NSUpArrowFunctionKey);
259 // Going up with no item selected should start the selection at the last
261 [controller() keyDown:event];
262 EXPECT_EQ([[controller() items] objectAtIndex:0], GetHighlightedItem());
264 [controller() keyDown:event];
265 EXPECT_EQ([[controller() items] objectAtIndex:1], GetHighlightedItem());
267 // There are no more items now so going up should stay at the first item.
268 [controller() keyDown:event];
269 EXPECT_EQ([[controller() items] objectAtIndex:1], GetHighlightedItem());