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/website_settings/website_settings_bubble_controller.h"
7 #include "base/i18n/rtl.h"
8 #include "base/strings/utf_string_conversions.h"
9 #include "chrome/browser/ui/cocoa/cocoa_test_helper.h"
10 #include "testing/gtest_mac.h"
12 @interface WebsiteSettingsBubbleController (ExposedForTesting)
13 - (NSSegmentedControl*)segmentedControl;
14 - (NSTabView*)tabView;
15 - (NSView*)permissionsView;
16 - (NSView*)connectionTabContentView;
17 - (NSImageView*)identityStatusIcon;
18 - (NSTextField*)identityStatusDescriptionField;
19 - (NSImageView*)connectionStatusIcon;
20 - (NSTextField*)connectionStatusDescriptionField;
21 - (NSButton*)helpButton;
24 @implementation WebsiteSettingsBubbleController (ExposedForTesting)
25 - (NSSegmentedControl*)segmentedControl {
26 return segmentedControl_.get();
28 - (NSTabView*)tabView {
29 return tabView_.get();
31 - (NSView*)permissionsView {
32 return permissionsView_;
35 - (NSView*)connectionTabContentView {
36 return connectionTabContentView_;
39 - (NSImageView*)identityStatusIcon {
40 return identityStatusIcon_;
43 - (NSTextField*)identityStatusDescriptionField {
44 return identityStatusDescriptionField_;
47 - (NSImageView*)connectionStatusIcon {
48 return connectionStatusIcon_;
51 - (NSTextField*)connectionStatusDescriptionField {
52 return connectionStatusDescriptionField_;
55 - (NSButton*)helpButton {
60 @interface WebsiteSettingsBubbleControllerForTesting
61 : WebsiteSettingsBubbleController {
63 CGFloat defaultWindowWidth_;
67 @implementation WebsiteSettingsBubbleControllerForTesting
68 - (void)setDefaultWindowWidth:(CGFloat)width {
69 defaultWindowWidth_ = width;
71 - (CGFloat)defaultWindowWidth {
72 // If |defaultWindowWidth_| is 0, use the superclass implementation.
73 return defaultWindowWidth_ ?
74 defaultWindowWidth_ : [super defaultWindowWidth];
80 // Indices of the menu items in the permission menu.
81 enum PermissionMenuIndices {
82 kMenuIndexContentSettingAllow = 0,
83 kMenuIndexContentSettingBlock,
84 kMenuIndexContentSettingDefault
87 const ContentSettingsType kTestPermissionTypes[] = {
88 // NOTE: FULLSCREEN does not support "Always block", so it must appear as
89 // one of the first three permissions.
90 CONTENT_SETTINGS_TYPE_FULLSCREEN,
91 CONTENT_SETTINGS_TYPE_IMAGES,
92 CONTENT_SETTINGS_TYPE_JAVASCRIPT,
93 CONTENT_SETTINGS_TYPE_PLUGINS,
94 CONTENT_SETTINGS_TYPE_POPUPS,
95 CONTENT_SETTINGS_TYPE_GEOLOCATION,
96 CONTENT_SETTINGS_TYPE_NOTIFICATIONS,
97 CONTENT_SETTINGS_TYPE_MOUSELOCK,
98 CONTENT_SETTINGS_TYPE_MEDIASTREAM_MIC
101 const ContentSetting kTestSettings[] = {
102 CONTENT_SETTING_DEFAULT,
103 CONTENT_SETTING_DEFAULT,
104 CONTENT_SETTING_DEFAULT,
105 CONTENT_SETTING_ALLOW,
106 CONTENT_SETTING_BLOCK,
107 CONTENT_SETTING_ALLOW,
108 CONTENT_SETTING_BLOCK,
109 CONTENT_SETTING_ALLOW,
110 CONTENT_SETTING_BLOCK
113 const ContentSetting kTestDefaultSettings[] = {
114 CONTENT_SETTING_ALLOW,
115 CONTENT_SETTING_BLOCK,
119 const content_settings::SettingSource kTestSettingSources[] = {
120 content_settings::SETTING_SOURCE_USER,
121 content_settings::SETTING_SOURCE_USER,
122 content_settings::SETTING_SOURCE_USER,
123 content_settings::SETTING_SOURCE_USER,
124 content_settings::SETTING_SOURCE_USER,
125 content_settings::SETTING_SOURCE_POLICY,
126 content_settings::SETTING_SOURCE_POLICY,
127 content_settings::SETTING_SOURCE_EXTENSION,
128 content_settings::SETTING_SOURCE_EXTENSION
131 class WebsiteSettingsBubbleControllerTest : public CocoaTest {
133 WebsiteSettingsBubbleControllerTest() {
137 void TearDown() override {
139 CocoaTest::TearDown();
143 WebsiteSettingsUIBridge* bridge_; // Weak, owned by controller.
150 // Creates a new website settings bubble, with the given default width.
151 // If |default_width| is 0, the *default* default width will be used.
152 void CreateBubbleWithWidth(CGFloat default_width) {
153 bridge_ = new WebsiteSettingsUIBridge(nullptr);
155 // The controller cleans up after itself when the window closes.
156 controller_ = [WebsiteSettingsBubbleControllerForTesting alloc];
157 [controller_ setDefaultWindowWidth:default_width];
158 [controller_ initWithParentWindow:test_window()
159 websiteSettingsUIBridge:bridge_
162 window_ = [controller_ window];
163 [controller_ showWindow:nil];
166 void CreateBubble() {
167 CreateBubbleWithWidth(0.0);
170 // Return a pointer to the first NSTextField found that either matches, or
171 // doesn't match, the given text.
172 NSTextField* FindTextField(MatchType match_type, NSString* text) {
173 // The window's only immediate child is an invisible view that has a flipped
174 // coordinate origin. It is into this that all views get placed.
175 NSArray* window_subviews = [[window_ contentView] subviews];
176 EXPECT_EQ(1U, [window_subviews count]);
177 NSArray* subviews = [[window_subviews lastObject] subviews];
179 // Expect 4 views: the identity, identity status, the segmented control
180 // (which implements the tab strip), and the tab view.
181 EXPECT_EQ(4U, [subviews count]);
183 bool desired_result = match_type == TEXT_EQUAL;
185 for (NSView* view in subviews) {
186 if ([view isKindOfClass:[NSTextField class]]) {
187 NSTextField* text_field = static_cast<NSTextField*>(view);
188 if ([[text_field stringValue] isEqual:text] == desired_result)
195 NSMutableArray* FindAllSubviewsOfClass(NSView* parent_view, Class a_class) {
196 NSMutableArray* views = [NSMutableArray array];
197 for (NSView* view in [parent_view subviews]) {
198 if ([view isKindOfClass:a_class])
199 [views addObject:view];
204 // Sets up the dialog with some test permission settings.
205 void SetTestPermissions() {
206 // Create a list of 5 different permissions, corresponding to all the
207 // possible settings:
208 // - [allow, block, ask] by default
209 // - [block, allow] * [by user, by policy, by extension]
210 PermissionInfoList list;
211 WebsiteSettingsUI::PermissionInfo info;
212 for (size_t i = 0; i < arraysize(kTestPermissionTypes); ++i) {
213 info.type = kTestPermissionTypes[i];
214 info.setting = kTestSettings[i];
215 if (info.setting == CONTENT_SETTING_DEFAULT)
216 info.default_setting = kTestDefaultSettings[i];
217 info.source = kTestSettingSources[i];
218 list.push_back(info);
220 bridge_->SetPermissionInfo(list);
223 WebsiteSettingsBubbleControllerForTesting* controller_; // Weak, owns self.
224 NSWindow* window_; // Weak, owned by controller.
227 TEST_F(WebsiteSettingsBubbleControllerTest, BasicIdentity) {
228 WebsiteSettingsUI::IdentityInfo info;
229 info.site_identity = std::string("nhl.com");
230 info.identity_status = WebsiteSettings::SITE_IDENTITY_STATUS_UNKNOWN;
234 // Test setting the site identity.
235 bridge_->SetIdentityInfo(const_cast<WebsiteSettingsUI::IdentityInfo&>(info));
236 NSTextField* identity_field = FindTextField(TEXT_EQUAL, @"nhl.com");
237 ASSERT_TRUE(identity_field != nil);
239 // Test changing the site identity, and ensure that the UI is updated.
240 info.site_identity = std::string("google.com");
241 bridge_->SetIdentityInfo(const_cast<WebsiteSettingsUI::IdentityInfo&>(info));
242 EXPECT_EQ(identity_field, FindTextField(TEXT_EQUAL, @"google.com"));
244 // Find the identity status field.
245 NSTextField* identity_status_field =
246 FindTextField(TEXT_NOT_EQUAL, @"google.com");
247 ASSERT_NE(identity_field, identity_status_field);
249 // Ensure the text of the identity status field changes when the status does.
250 NSString* status = [identity_status_field stringValue];
251 info.identity_status = WebsiteSettings::SITE_IDENTITY_STATUS_CERT;
252 bridge_->SetIdentityInfo(const_cast<WebsiteSettingsUI::IdentityInfo&>(info));
253 EXPECT_NSNE(status, [identity_status_field stringValue]);
256 TEST_F(WebsiteSettingsBubbleControllerTest, SetIdentityInfo) {
257 WebsiteSettingsUI::IdentityInfo info;
258 info.site_identity = std::string("nhl.com");
259 info.identity_status = WebsiteSettings::SITE_IDENTITY_STATUS_UNKNOWN;
260 info.identity_status_description = std::string("Identity1");
261 info.connection_status = WebsiteSettings::SITE_CONNECTION_STATUS_UNKNOWN;
262 info.connection_status_description = std::string("Connection1");
267 // Set the identity, and test that the description fields on the Connection
268 // tab are set properly.
269 bridge_->SetIdentityInfo(const_cast<WebsiteSettingsUI::IdentityInfo&>(info));
270 EXPECT_NSEQ(@"Identity1",
271 [[controller_ identityStatusDescriptionField] stringValue]);
272 EXPECT_NSEQ(@"Connection1",
273 [[controller_ connectionStatusDescriptionField] stringValue]);
275 // Check the contents of the images, and make sure they change after the
278 NSImage* identity_icon = [[controller_ identityStatusIcon] image];
279 NSImage* connection_icon = [[controller_ connectionStatusIcon] image];
280 // Icons should be the same when they are both unknown.
281 EXPECT_EQ(identity_icon, connection_icon);
283 // Ensure that the link button for certificate info is not there -- the
284 // help link button should be the first one found.
285 NSMutableArray* buttons = FindAllSubviewsOfClass(
286 [controller_ connectionTabContentView], [NSButton class]);
287 ASSERT_EQ(1U, [buttons count]);
288 EXPECT_NSEQ([controller_ helpButton], [buttons objectAtIndex:0]);
290 // Check that it has a target and action linked up.
291 NSButton* link_button = static_cast<NSButton*>([buttons objectAtIndex:0]);
292 EXPECT_NSEQ(controller_, [link_button target]);
293 EXPECT_TRUE([link_button action] == @selector(showHelpPage:));
295 info.identity_status = WebsiteSettings::SITE_IDENTITY_STATUS_CERT;
296 info.connection_status = WebsiteSettings::SITE_CONNECTION_STATUS_ENCRYPTED;
298 bridge_->SetIdentityInfo(const_cast<WebsiteSettingsUI::IdentityInfo&>(info));
300 EXPECT_NE(identity_icon, [[controller_ identityStatusIcon] image]);
301 EXPECT_NE(connection_icon, [[controller_ connectionStatusIcon] image]);
303 // The certificate info button should be there now.
304 buttons = FindAllSubviewsOfClass(
305 [controller_ connectionTabContentView], [NSButton class]);
306 ASSERT_EQ(2U, [buttons count]);
307 EXPECT_NSNE([controller_ helpButton], [buttons objectAtIndex:1]);
309 // Check that it has a target and action linked up.
310 link_button = static_cast<NSButton*>([buttons objectAtIndex:1]);
311 EXPECT_NSEQ(controller_, [link_button target]);
312 EXPECT_TRUE([link_button action] == @selector(showCertificateInfo:));
315 TEST_F(WebsiteSettingsBubbleControllerTest, SetPermissionInfo) {
317 SetTestPermissions();
319 // There should be three subviews per permission (an icon, a label and a
320 // select box), plus a text label for the Permission section.
321 NSArray* subviews = [[controller_ permissionsView] subviews];
322 EXPECT_EQ(arraysize(kTestPermissionTypes) * 3 + 1, [subviews count]);
324 // Ensure that there is a distinct label for each permission.
325 NSMutableSet* labels = [NSMutableSet set];
326 for (NSView* view in subviews) {
327 if ([view isKindOfClass:[NSTextField class]])
328 [labels addObject:[static_cast<NSTextField*>(view) stringValue]];
330 // The section header ("Permissions") will also be found, hence the +1.
331 EXPECT_EQ(arraysize(kTestPermissionTypes) + 1, [labels count]);
333 // Ensure that the button labels are distinct, and look for the correct
334 // number of disabled buttons.
335 int disabled_count = 0;
336 [labels removeAllObjects];
337 for (NSView* view in subviews) {
338 if ([view isKindOfClass:[NSPopUpButton class]]) {
339 NSPopUpButton* button = static_cast<NSPopUpButton*>(view);
340 [labels addObject:[[button selectedCell] title]];
342 if (![button isEnabled])
346 EXPECT_EQ(arraysize(kTestPermissionTypes), [labels count]);
348 // 4 of the buttons should be disabled -- the ones that have a setting source
349 // of SETTING_SOURCE_POLICY or SETTING_SOURCE_EXTENSION.
350 EXPECT_EQ(4, disabled_count);
353 TEST_F(WebsiteSettingsBubbleControllerTest, SetSelectedTab) {
355 NSSegmentedControl* segmentedControl = [controller_ segmentedControl];
356 NSTabView* tabView = [controller_ tabView];
358 // Test whether SetSelectedTab properly changes both the segmented control
359 // (which implements the tabs) as well as the visible tab contents.
360 // NOTE: This implicitly (and deliberately) tests that the tabs appear in a
361 // specific order: Permissions, Connection.
362 EXPECT_EQ(0, [segmentedControl selectedSegment]);
363 EXPECT_EQ(0, [tabView indexOfTabViewItem:[tabView selectedTabViewItem]]);
364 bridge_->SetSelectedTab(WebsiteSettingsUI::TAB_ID_CONNECTION);
365 EXPECT_EQ(1, [segmentedControl selectedSegment]);
366 EXPECT_EQ(1, [tabView indexOfTabViewItem:[tabView selectedTabViewItem]]);
369 TEST_F(WebsiteSettingsBubbleControllerTest, WindowWidth) {
370 const CGFloat kBigEnoughBubbleWidth = 310;
371 // Creating a window that should fit everything.
372 CreateBubbleWithWidth(kBigEnoughBubbleWidth);
373 SetTestPermissions();
375 CGFloat window_width = NSWidth([[controller_ window] frame]);
377 // Check the window was made bigger to fit the content.
378 EXPECT_EQ(kBigEnoughBubbleWidth, window_width);
380 // Check that the window is wider than the right edge of all the permission
381 // popup buttons (LTR locales) or wider than the left edge (RTL locales).
382 bool is_rtl = base::i18n::IsRTL();
383 for (NSView* view in [[controller_ permissionsView] subviews]) {
385 if ([view isKindOfClass:[NSPopUpButton class]]) {
386 NSPopUpButton* button = static_cast<NSPopUpButton*>(view);
387 EXPECT_GT(NSMinX([button frame]), 0);
389 if ([view isKindOfClass:[NSImageView class]]) {
390 NSImageView* icon = static_cast<NSImageView*>(view);
391 EXPECT_LT(NSMaxX([icon frame]), window_width);
394 if ([view isKindOfClass:[NSImageView class]]) {
395 NSImageView* icon = static_cast<NSImageView*>(view);
396 EXPECT_GT(NSMinX([icon frame]), 0);
398 if ([view isKindOfClass:[NSPopUpButton class]]) {
399 NSPopUpButton* button = static_cast<NSPopUpButton*>(view);
400 EXPECT_LT(NSMaxX([button frame]), window_width);