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