// 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/website_settings_bubble_controller.h" #include "base/utf_string_conversions.h" #include "chrome/browser/ui/cocoa/cocoa_test_helper.h" #include "testing/gtest_mac.h" @interface WebsiteSettingsBubbleController (ExposedForTesting) - (NSSegmentedControl*)segmentedControl; - (NSTabView*)tabView; - (NSView*)permissionsView; - (NSView*)connectionTabContentView; - (NSImageView*)identityStatusIcon; - (NSTextField*)identityStatusDescriptionField; - (NSImageView*)connectionStatusIcon; - (NSTextField*)connectionStatusDescriptionField; - (NSTextField*)firstVisitDescriptionField; - (NSButton*)helpButton; @end @implementation WebsiteSettingsBubbleController (ExposedForTesting) - (NSSegmentedControl*)segmentedControl { return segmentedControl_.get(); } - (NSTabView*)tabView { return tabView_.get(); } - (NSView*)permissionsView { return permissionsView_; } - (NSView*)connectionTabContentView { return connectionTabContentView_; } - (NSImageView*)identityStatusIcon { return identityStatusIcon_; } - (NSTextField*)identityStatusDescriptionField { return identityStatusDescriptionField_; } - (NSImageView*)connectionStatusIcon { return connectionStatusIcon_; } - (NSTextField*)connectionStatusDescriptionField { return connectionStatusDescriptionField_; } - (NSTextField*)firstVisitDescriptionField { return firstVisitDescriptionField_; } - (NSButton*)helpButton { return helpButton_; } @end @interface WebsiteSettingsBubbleControllerForTesting : WebsiteSettingsBubbleController { @private CGFloat defaultWindowWidth_; } @end @implementation WebsiteSettingsBubbleControllerForTesting - (void)setDefaultWindowWidth:(CGFloat)width { defaultWindowWidth_ = width; } - (CGFloat)defaultWindowWidth { // If |defaultWindowWidth_| is 0, use the superclass implementation. return defaultWindowWidth_ ? defaultWindowWidth_ : [super defaultWindowWidth]; } @end namespace { // Indices of the menu items in the permission menu. enum PermissionMenuIndices { kMenuIndexContentSettingAllow = 0, kMenuIndexContentSettingBlock, kMenuIndexContentSettingDefault }; const ContentSettingsType kTestPermissionTypes[] = { // NOTE: FULLSCREEN does not support "Always block", so it must appear as // one of the first three permissions. CONTENT_SETTINGS_TYPE_FULLSCREEN, CONTENT_SETTINGS_TYPE_IMAGES, CONTENT_SETTINGS_TYPE_JAVASCRIPT, CONTENT_SETTINGS_TYPE_PLUGINS, CONTENT_SETTINGS_TYPE_POPUPS, CONTENT_SETTINGS_TYPE_GEOLOCATION, CONTENT_SETTINGS_TYPE_NOTIFICATIONS, CONTENT_SETTINGS_TYPE_MOUSELOCK, CONTENT_SETTINGS_TYPE_MEDIASTREAM, }; const ContentSetting kTestSettings[] = { CONTENT_SETTING_DEFAULT, CONTENT_SETTING_DEFAULT, CONTENT_SETTING_DEFAULT, CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK, CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK, }; const ContentSetting kTestDefaultSettings[] = { CONTENT_SETTING_ALLOW, CONTENT_SETTING_BLOCK, CONTENT_SETTING_ASK }; const content_settings::SettingSource kTestSettingSources[] = { content_settings::SETTING_SOURCE_USER, content_settings::SETTING_SOURCE_USER, content_settings::SETTING_SOURCE_USER, content_settings::SETTING_SOURCE_USER, content_settings::SETTING_SOURCE_USER, content_settings::SETTING_SOURCE_POLICY, content_settings::SETTING_SOURCE_POLICY, content_settings::SETTING_SOURCE_EXTENSION, content_settings::SETTING_SOURCE_EXTENSION, }; class WebsiteSettingsBubbleControllerTest : public CocoaTest { public: WebsiteSettingsBubbleControllerTest() { controller_ = nil; } virtual void TearDown() { [controller_ close]; CocoaTest::TearDown(); } protected: WebsiteSettingsUIBridge* bridge_; // Weak, owned by controller. enum MatchType { TEXT_EQUAL = 0, TEXT_NOT_EQUAL }; // Creates a new website settings bubble, with the given default width. // If |default_width| is 0, the *default* default width will be used. void CreateBubbleWithWidth(CGFloat default_width) { bridge_ = new WebsiteSettingsUIBridge(); // The controller cleans up after itself when the window closes. controller_ = [WebsiteSettingsBubbleControllerForTesting alloc]; [controller_ setDefaultWindowWidth:default_width]; [controller_ initWithParentWindow:test_window() websiteSettingsUIBridge:bridge_ webContents:nil isInternalPage:NO]; window_ = [controller_ window]; [controller_ showWindow:nil]; } void CreateBubble() { CreateBubbleWithWidth(0.0); } // Return a pointer to the first NSTextField found that either matches, or // doesn't match, the given text. NSTextField* FindTextField(MatchType match_type, NSString* text) { // The window's only immediate child is an invisible view that has a flipped // coordinate origin. It is into this that all views get placed. NSArray* window_subviews = [[window_ contentView] subviews]; EXPECT_EQ(1U, [window_subviews count]); NSArray* subviews = [[window_subviews lastObject] subviews]; // Expect 4 views: the identity, identity status, the segmented control // (which implements the tab strip), and the tab view. EXPECT_EQ(4U, [subviews count]); bool desired_result = match_type == TEXT_EQUAL; for (NSView* view in subviews) { if ([view isKindOfClass:[NSTextField class]]) { NSTextField* text_field = static_cast(view); if ([[text_field stringValue] isEqual:text] == desired_result) return text_field; } } return nil; } NSMutableArray* FindAllSubviewsOfClass(NSView* parent_view, Class a_class) { NSMutableArray* views = [NSMutableArray array]; for (NSView* view in [parent_view subviews]) { if ([view isKindOfClass:a_class]) [views addObject:view]; } return views; } // Sets up the dialog with some test permission settings. void SetTestPermissions() { // Create a list of 5 different permissions, corresponding to all the // possible settings: // - [allow, block, ask] by default // - [block, allow] * [by user, by policy, by extension] PermissionInfoList list; WebsiteSettingsUI::PermissionInfo info; for (size_t i = 0; i < arraysize(kTestPermissionTypes); ++i) { info.type = kTestPermissionTypes[i]; info.setting = kTestSettings[i]; if (info.setting == CONTENT_SETTING_DEFAULT) info.default_setting = kTestDefaultSettings[i]; info.source = kTestSettingSources[i]; list.push_back(info); } bridge_->SetPermissionInfo(list); } WebsiteSettingsBubbleControllerForTesting* controller_; // Weak, owns self. NSWindow* window_; // Weak, owned by controller. }; TEST_F(WebsiteSettingsBubbleControllerTest, BasicIdentity) { WebsiteSettingsUI::IdentityInfo info; info.site_identity = std::string("nhl.com"); info.identity_status = WebsiteSettings::SITE_IDENTITY_STATUS_UNKNOWN; CreateBubble(); // Test setting the site identity. bridge_->SetIdentityInfo(const_cast(info)); NSTextField* identity_field = FindTextField(TEXT_EQUAL, @"nhl.com"); ASSERT_TRUE(identity_field != nil); // Test changing the site identity, and ensure that the UI is updated. info.site_identity = std::string("google.com"); bridge_->SetIdentityInfo(const_cast(info)); EXPECT_EQ(identity_field, FindTextField(TEXT_EQUAL, @"google.com")); // Find the identity status field. NSTextField* identity_status_field = FindTextField(TEXT_NOT_EQUAL, @"google.com"); ASSERT_NE(identity_field, identity_status_field); // Ensure the text of the identity status field changes when the status does. NSString* status = [identity_status_field stringValue]; info.identity_status = WebsiteSettings::SITE_IDENTITY_STATUS_CERT; bridge_->SetIdentityInfo(const_cast(info)); EXPECT_NSNE(status, [identity_status_field stringValue]); } TEST_F(WebsiteSettingsBubbleControllerTest, SetIdentityInfo) { WebsiteSettingsUI::IdentityInfo info; info.site_identity = std::string("nhl.com"); info.identity_status = WebsiteSettings::SITE_IDENTITY_STATUS_UNKNOWN; info.identity_status_description = std::string("Identity1"); info.connection_status = WebsiteSettings::SITE_CONNECTION_STATUS_UNKNOWN; info.connection_status_description = std::string("Connection1"); info.cert_id = 0; CreateBubble(); // Set the identity, and test that the description fields on the Connection // tab are set properly. bridge_->SetIdentityInfo(const_cast(info)); EXPECT_NSEQ(@"Identity1", [[controller_ identityStatusDescriptionField] stringValue]); EXPECT_NSEQ(@"Connection1", [[controller_ connectionStatusDescriptionField] stringValue]); // Check the contents of the images, and make sure they change after the // status changes. NSImage* identity_icon = [[controller_ identityStatusIcon] image]; NSImage* connection_icon = [[controller_ connectionStatusIcon] image]; // Icons should be the same when they are both unknown. EXPECT_EQ(identity_icon, connection_icon); // Ensure that the link button for certificate info is not there -- the // help link button should be the first one found. NSMutableArray* buttons = FindAllSubviewsOfClass( [controller_ connectionTabContentView], [NSButton class]); ASSERT_EQ(1U, [buttons count]); EXPECT_NSEQ([controller_ helpButton], [buttons objectAtIndex:0]); // Check that it has a target and action linked up. NSButton* link_button = static_cast([buttons objectAtIndex:0]); EXPECT_NSEQ(controller_, [link_button target]); EXPECT_TRUE([link_button action] == @selector(showHelpPage:)); info.identity_status = WebsiteSettings::SITE_IDENTITY_STATUS_CERT; info.connection_status = WebsiteSettings::SITE_CONNECTION_STATUS_ENCRYPTED; info.cert_id = 1; bridge_->SetIdentityInfo(const_cast(info)); EXPECT_NE(identity_icon, [[controller_ identityStatusIcon] image]); EXPECT_NE(connection_icon, [[controller_ connectionStatusIcon] image]); // The certificate info button should be there now. buttons = FindAllSubviewsOfClass( [controller_ connectionTabContentView], [NSButton class]); ASSERT_EQ(2U, [buttons count]); EXPECT_NSNE([controller_ helpButton], [buttons objectAtIndex:1]); // Check that it has a target and action linked up. link_button = static_cast([buttons objectAtIndex:1]); EXPECT_NSEQ(controller_, [link_button target]); EXPECT_TRUE([link_button action] == @selector(showCertificateInfo:)); } TEST_F(WebsiteSettingsBubbleControllerTest, SetFirstVisit) { CreateBubble(); bridge_->SetFirstVisit(ASCIIToUTF16("Yesterday")); EXPECT_NSEQ(@"Yesterday", [[controller_ firstVisitDescriptionField] stringValue]); } TEST_F(WebsiteSettingsBubbleControllerTest, SetPermissionInfo) { CreateBubble(); SetTestPermissions(); // There should be three subviews per permission (an icon, a label and a // select box), plus a text label for the Permission section. NSArray* subviews = [[controller_ permissionsView] subviews]; EXPECT_EQ(arraysize(kTestPermissionTypes) * 3 + 1, [subviews count]); // Ensure that there is a distinct label for each permission. NSMutableSet* labels = [NSMutableSet set]; for (NSView* view in subviews) { if ([view isKindOfClass:[NSTextField class]]) [labels addObject:[static_cast(view) stringValue]]; } // The section header ("Permissions") will also be found, hence the +1. EXPECT_EQ(arraysize(kTestPermissionTypes) + 1, [labels count]); // Ensure that the button labels are distinct, and look for the correct // number of disabled buttons. int disabled_count = 0; [labels removeAllObjects]; for (NSView* view in subviews) { if ([view isKindOfClass:[NSPopUpButton class]]) { NSPopUpButton* button = static_cast(view); [labels addObject:[[button selectedCell] title]]; if (![button isEnabled]) ++disabled_count; } } EXPECT_EQ(arraysize(kTestPermissionTypes), [labels count]); // 4 of the buttons should be disabled -- the ones that have a setting source // of SETTING_SOURCE_POLICY or SETTING_SOURCE_EXTENSION. EXPECT_EQ(4, disabled_count); } TEST_F(WebsiteSettingsBubbleControllerTest, SetSelectedTab) { CreateBubble(); NSSegmentedControl* segmentedControl = [controller_ segmentedControl]; NSTabView* tabView = [controller_ tabView]; // Test whether SetSelectedTab properly changes both the segmented control // (which implements the tabs) as well as the visible tab contents. // NOTE: This implicitly (and deliberately) tests that the tabs appear in a // specific order: Permissions, Connection. EXPECT_EQ(0, [segmentedControl selectedSegment]); EXPECT_EQ(0, [tabView indexOfTabViewItem:[tabView selectedTabViewItem]]); bridge_->SetSelectedTab(WebsiteSettingsUI::TAB_ID_CONNECTION); EXPECT_EQ(1, [segmentedControl selectedSegment]); EXPECT_EQ(1, [tabView indexOfTabViewItem:[tabView selectedTabViewItem]]); } TEST_F(WebsiteSettingsBubbleControllerTest, WindowWidth) { // Try creating a window that is obviously too small. CreateBubbleWithWidth(30.0); SetTestPermissions(); CGFloat window_width = NSWidth([[controller_ window] frame]); // Check the window was made bigger to fit the content. EXPECT_LT(30.0, window_width); // Check that the window is wider than the right edge of all the permission // popup buttons. for (NSView* view in [[controller_ permissionsView] subviews]) { if ([view isKindOfClass:[NSPopUpButton class]]) { NSPopUpButton* button = static_cast(view); EXPECT_LT(NSMaxX([button frame]), window_width); } } } } // namespace