blob: e91fb3b2e5238df1380bc86c1ce8ad78139a2a6a [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/browser/avatar_menu_bubble_controller.h"
#include "base/mac/scoped_nsobject.h"
#include "base/memory/scoped_ptr.h"
#include "base/message_loop/message_pump_mac.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/prefs/pref_service_syncable.h"
#include "chrome/browser/profiles/avatar_menu_model.h"
#include "chrome/browser/profiles/avatar_menu_model_observer.h"
#include "chrome/browser/profiles/profile_info_cache.h"
#import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile_manager.h"
#include "testing/gtest_mac.h"
#import "ui/base/cocoa/controls/hyperlink_button_cell.h"
#include "ui/base/test/cocoa_test_event_utils.h"
class AvatarMenuBubbleControllerTest : public CocoaTest {
public:
AvatarMenuBubbleControllerTest()
: manager_(TestingBrowserProcess::GetGlobal()) {
}
virtual void SetUp() {
CocoaTest::SetUp();
ASSERT_TRUE(manager_.SetUp());
manager_.CreateTestingProfile("test1", scoped_ptr<PrefServiceSyncable>(),
ASCIIToUTF16("Test 1"), 1, std::string());
manager_.CreateTestingProfile("test2", scoped_ptr<PrefServiceSyncable>(),
ASCIIToUTF16("Test 2"), 0, std::string());
model_ = new AvatarMenuModel(manager_.profile_info_cache(), NULL, NULL);
NSRect frame = [test_window() frame];
NSPoint point = NSMakePoint(NSMidX(frame), NSMidY(frame));
controller_ =
[[AvatarMenuBubbleController alloc] initWithModel:model()
parentWindow:test_window()
anchoredAt:point];
}
TestingProfileManager* manager() { return &manager_; }
AvatarMenuBubbleController* controller() { return controller_; }
AvatarMenuModel* model() { return model_; }
AvatarMenuItemController* GetHighlightedItem() {
for (AvatarMenuItemController* item in [controller() items]) {
if ([item isHighlighted])
return item;
}
return nil;
}
private:
TestingProfileManager manager_;
// Weak; releases self.
AvatarMenuBubbleController* controller_;
// Weak; owned by |controller_|.
AvatarMenuModel* model_;
};
TEST_F(AvatarMenuBubbleControllerTest, InitialLayout) {
[controller() showWindow:nil];
// Two profiles means two item views and the new button with separator.
NSView* contents = [[controller() window] contentView];
EXPECT_EQ(4U, [[contents subviews] count]);
// Loop over the itmes and match the viewController views to subviews.
NSMutableArray* subviews =
[NSMutableArray arrayWithArray:[contents subviews]];
for (AvatarMenuItemController* viewController in [controller() items]) {
for (NSView* subview in subviews) {
if ([viewController view] == subview) {
[subviews removeObject:subview];
break;
}
}
}
// The one remaining subview should be the new user button.
EXPECT_EQ(2U, [subviews count]);
BOOL hasButton = NO;
BOOL hasSeparator = NO;
for (NSView* subview in subviews) {
if ([subview isKindOfClass:[NSButton class]]) {
EXPECT_FALSE(hasButton);
hasButton = YES;
NSButton* button = static_cast<NSButton*>(subview);
EXPECT_EQ(@selector(newProfile:), [button action]);
EXPECT_EQ(controller(), [button target]);
EXPECT_TRUE([[button cell] isKindOfClass:[HyperlinkButtonCell class]]);
} else if ([subview isKindOfClass:[NSBox class]]) {
EXPECT_FALSE(hasSeparator);
hasSeparator = YES;
} else {
EXPECT_FALSE(subview) << "Unexpected subview: "
<< [[subview description] UTF8String];
}
}
[controller() close];
}
TEST_F(AvatarMenuBubbleControllerTest, PerformLayout) {
[controller() showWindow:nil];
NSView* contents = [[controller() window] contentView];
EXPECT_EQ(4U, [[contents subviews] count]);
base::scoped_nsobject<NSMutableArray> oldItems([[controller() items] copy]);
// Now create a new profile and notify the delegate.
manager()->CreateTestingProfile("test3", scoped_ptr<PrefServiceSyncable>(),
ASCIIToUTF16("Test 3"), 0, std::string());
// Testing the bridge is not worth the effort...
[controller() performLayout];
EXPECT_EQ(5U, [[contents subviews] count]);
// Make sure that none of the old items exit.
NSArray* newItems = [controller() items];
for (AvatarMenuItemController* oldVC in oldItems.get()) {
EXPECT_FALSE([newItems containsObject:oldVC]);
EXPECT_FALSE([[contents subviews] containsObject:[oldVC view]]);
}
[controller() close];
}
// This subclass is used to inject a delegate into the hide/show edit link
// animation.
@interface TestingAvatarMenuItemController : AvatarMenuItemController
<NSAnimationDelegate> {
@private
scoped_ptr<base::MessagePumpNSRunLoop> pump_;
}
// After calling |-highlightForEventType:| an animation will possibly be
// started. Since the animation is non-blocking, the run loop will need to be
// spun (via the MessagePump) until the animation has finished.
- (void)runMessagePump;
@end
@implementation TestingAvatarMenuItemController
- (void)runMessagePump {
if (!pump_)
pump_.reset(new base::MessagePumpNSRunLoop);
pump_->Run(NULL);
}
- (void)willStartAnimation:(NSAnimation*)anim {
[anim setDelegate:self];
}
- (void)animationDidEnd:(NSAnimation*)anim {
[super animationDidEnd:anim];
pump_->Quit();
}
- (void)animationDidStop:(NSAnimation*)anim {
[super animationDidStop:anim];
FAIL() << "Animation stopped before it completed its run";
pump_->Quit();
}
- (void)sendHighlightMessageForMouseExited {
[self highlightForEventType:NSMouseExited];
// Quit the pump because the animation was cancelled before it even ran.
pump_->Quit();
}
@end
TEST_F(AvatarMenuBubbleControllerTest, HighlightForEventType) {
base::scoped_nsobject<TestingAvatarMenuItemController> item(
[[TestingAvatarMenuItemController alloc] initWithModelIndex:0
menuController:nil]);
// Test non-active states first.
[[item activeView] setHidden:YES];
NSView* editButton = [item editButton];
NSView* emailField = [item emailField];
// The edit link remains hidden.
[item setIsHighlighted:YES];
EXPECT_TRUE(editButton.isHidden);
EXPECT_FALSE(emailField.isHidden);
[item setIsHighlighted:NO];
EXPECT_TRUE(editButton.isHidden);
EXPECT_FALSE(emailField.isHidden);
// Make the item "active" and re-test.
[[item activeView] setHidden:NO];
[item setIsHighlighted:YES];
[item runMessagePump];
EXPECT_FALSE(editButton.isHidden);
EXPECT_TRUE(emailField.isHidden);
[item setIsHighlighted:NO];
[item runMessagePump];
EXPECT_TRUE(editButton.isHidden);
EXPECT_FALSE(emailField.isHidden);
// Now mouse over and out quickly, as if scrubbing through the menu, to test
// the hover dwell delay.
[item highlightForEventType:NSMouseEntered];
[item performSelector:@selector(sendHighlightMessageForMouseExited)
withObject:nil
afterDelay:0];
[item runMessagePump];
EXPECT_TRUE(editButton.isHidden);
EXPECT_FALSE(emailField.isHidden);
}
TEST_F(AvatarMenuBubbleControllerTest, DownArrow) {
EXPECT_NSEQ(nil, GetHighlightedItem());
NSEvent* event =
cocoa_test_event_utils::KeyEventWithCharacter(NSDownArrowFunctionKey);
// Going down with no item selected should start the selection at the first
// item.
[controller() keyDown:event];
EXPECT_EQ([[controller() items] objectAtIndex:1], GetHighlightedItem());
[controller() keyDown:event];
EXPECT_EQ([[controller() items] objectAtIndex:0], GetHighlightedItem());
// There are no more items now so going down should stay at the last item.
[controller() keyDown:event];
EXPECT_EQ([[controller() items] objectAtIndex:0], GetHighlightedItem());
}
TEST_F(AvatarMenuBubbleControllerTest, UpArrow) {
EXPECT_NSEQ(nil, GetHighlightedItem());
NSEvent* event =
cocoa_test_event_utils::KeyEventWithCharacter(NSUpArrowFunctionKey);
// Going up with no item selected should start the selection at the last
// item.
[controller() keyDown:event];
EXPECT_EQ([[controller() items] objectAtIndex:0], GetHighlightedItem());
[controller() keyDown:event];
EXPECT_EQ([[controller() items] objectAtIndex:1], GetHighlightedItem());
// There are no more items now so going up should stay at the first item.
[controller() keyDown:event];
EXPECT_EQ([[controller() items] objectAtIndex:1], GetHighlightedItem());
}