| 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 |