1 // Copyright (c) 2012 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/omnibox/omnibox_popup_view_mac.h"
7 #include "base/memory/scoped_ptr.h"
8 #include "base/strings/sys_string_conversions.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
11 #import "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h"
12 #include "chrome/test/base/testing_profile.h"
13 #include "ui/gfx/rect.h"
14 #include "ui/gfx/text_elider.h"
18 const float kLargeWidth = 10000;
20 // Returns the length of the run starting at |location| for which
21 // |attributeName| remains the same.
22 NSUInteger RunLengthForAttribute(NSAttributedString* string,
24 NSString* attributeName) {
25 const NSRange full_range = NSMakeRange(0, [string length]);
27 [string attribute:attributeName
28 atIndex:location longestEffectiveRange:&range inRange:full_range];
30 // In order to signal when the run doesn't start exactly at location, return
31 // a weirdo length. This causes the incorrect expectation to manifest at the
32 // calling location, which is more useful than an EXPECT_EQ() would be here.
33 if (range.location != location) {
40 // Return true if the run starting at |location| has |color| for attribute
41 // NSForegroundColorAttributeName.
42 bool RunHasColor(NSAttributedString* string,
45 const NSRange full_range = NSMakeRange(0, [string length]);
47 NSColor* run_color = [string attribute:NSForegroundColorAttributeName
49 longestEffectiveRange:&range
52 // According to one "Ali Ozer", you can compare objects within the same color
53 // space using -isEqual:. Converting color spaces seems too heavyweight for
55 // http://lists.apple.com/archives/cocoa-dev/2005/May/msg00186.html
56 return [run_color isEqual:color] ? true : false;
59 // Return true if the run starting at |location| has the font trait(s) in |mask|
60 // font in NSFontAttributeName.
61 bool RunHasFontTrait(NSAttributedString* string,
63 NSFontTraitMask mask) {
64 const NSRange full_range = NSMakeRange(0, [string length]);
66 NSFont* run_font = [string attribute:NSFontAttributeName
68 longestEffectiveRange:&range
70 NSFontManager* fontManager = [NSFontManager sharedFontManager];
71 if (run_font && (mask == ([fontManager traitsOfFont:run_font] & mask))) {
77 // AutocompleteMatch doesn't really have the right constructor for our
78 // needs. Fake one for us to use.
79 AutocompleteMatch MakeMatch(const string16& contents,
80 const string16& description) {
81 AutocompleteMatch m(NULL, 1, true, AutocompleteMatchType::URL_WHAT_YOU_TYPED);
82 m.contents = contents;
83 m.description = description;
87 class MockOmniboxPopupViewMac : public OmniboxPopupViewMac {
89 MockOmniboxPopupViewMac(OmniboxView* omnibox_view,
90 OmniboxEditModel* edit_model,
92 : OmniboxPopupViewMac(omnibox_view, edit_model, field) {
95 void SetResultCount(size_t count) {
97 for (size_t i = 0; i < count; ++i)
98 matches.push_back(AutocompleteMatch());
100 result_.AppendMatches(matches);
104 virtual const AutocompleteResult& GetResult() const OVERRIDE {
109 AutocompleteResult result_;
112 class OmniboxPopupViewMacTest : public CocoaProfileTest {
114 OmniboxPopupViewMacTest() {
115 color_ = [NSColor blackColor];
116 dim_color_ = [NSColor darkGrayColor];
118 base::SysNSStringToUTF8([[NSFont userFontOfSize:12] fontName]), 12);
122 NSColor* color_; // weak
123 NSColor* dim_color_; // weak
127 DISALLOW_COPY_AND_ASSIGN(OmniboxPopupViewMacTest);
130 // Simple inputs with no matches should result in styled output who's text
131 // matches the input string, with the passed-in color, and nothing bolded.
132 TEST_F(OmniboxPopupViewMacTest, DecorateMatchedStringNoMatch) {
133 NSString* const string = @"This is a test";
134 AutocompleteMatch::ACMatchClassifications classifications;
136 NSAttributedString* decorated =
137 OmniboxPopupViewMac::DecorateMatchedString(
138 base::SysNSStringToUTF16(string), classifications,
139 color_, dim_color_, font_);
141 // Result has same characters as the input.
142 EXPECT_EQ([decorated length], [string length]);
143 EXPECT_TRUE([[decorated string] isEqualToString:string]);
145 // Our passed-in color for the entire string.
146 EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
147 NSForegroundColorAttributeName),
149 EXPECT_TRUE(RunHasColor(decorated, 0U, color_));
151 // An unbolded font for the entire string.
152 EXPECT_EQ(RunLengthForAttribute(decorated, 0U, NSFontAttributeName),
154 EXPECT_FALSE(RunHasFontTrait(decorated, 0U, NSBoldFontMask));
157 // Identical to DecorateMatchedStringNoMatch, except test that URL style gets a
158 // different color than we passed in.
159 TEST_F(OmniboxPopupViewMacTest, DecorateMatchedStringURLNoMatch) {
160 NSString* const string = @"This is a test";
161 AutocompleteMatch::ACMatchClassifications classifications;
163 classifications.push_back(
164 ACMatchClassification(0, ACMatchClassification::URL));
166 NSAttributedString* decorated =
167 OmniboxPopupViewMac::DecorateMatchedString(
168 base::SysNSStringToUTF16(string), classifications,
169 color_, dim_color_, font_);
171 // Result has same characters as the input.
172 EXPECT_EQ([decorated length], [string length]);
173 EXPECT_TRUE([[decorated string] isEqualToString:string]);
175 // One color for the entire string, and it's not the one we passed in.
176 EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
177 NSForegroundColorAttributeName),
179 EXPECT_FALSE(RunHasColor(decorated, 0U, color_));
181 // An unbolded font for the entire string.
182 EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
183 NSFontAttributeName), [string length]);
184 EXPECT_FALSE(RunHasFontTrait(decorated, 0U, NSBoldFontMask));
187 // Test that DIM works as expected.
188 TEST_F(OmniboxPopupViewMacTest, DecorateMatchedStringDimNoMatch) {
189 NSString* const string = @"This is a test";
191 const NSUInteger run_length_1 = 5, run_length_2 = 2, run_length_3 = 7;
192 // Make sure nobody messed up the inputs.
193 EXPECT_EQ(run_length_1 + run_length_2 + run_length_3, [string length]);
195 // Push each run onto classifications.
196 AutocompleteMatch::ACMatchClassifications classifications;
197 classifications.push_back(
198 ACMatchClassification(0, ACMatchClassification::NONE));
199 classifications.push_back(
200 ACMatchClassification(run_length_1, ACMatchClassification::DIM));
201 classifications.push_back(
202 ACMatchClassification(run_length_1 + run_length_2,
203 ACMatchClassification::NONE));
205 NSAttributedString* decorated =
206 OmniboxPopupViewMac::DecorateMatchedString(
207 base::SysNSStringToUTF16(string), classifications,
208 color_, dim_color_, font_);
210 // Result has same characters as the input.
211 EXPECT_EQ([decorated length], [string length]);
212 EXPECT_TRUE([[decorated string] isEqualToString:string]);
214 // Should have three font runs, normal, dim, normal.
215 EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
216 NSForegroundColorAttributeName),
218 EXPECT_TRUE(RunHasColor(decorated, 0U, color_));
220 EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1,
221 NSForegroundColorAttributeName),
223 EXPECT_TRUE(RunHasColor(decorated, run_length_1, dim_color_));
225 EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1 + run_length_2,
226 NSForegroundColorAttributeName),
228 EXPECT_TRUE(RunHasColor(decorated, run_length_1 + run_length_2, color_));
230 // An unbolded font for the entire string.
231 EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
232 NSFontAttributeName), [string length]);
233 EXPECT_FALSE(RunHasFontTrait(decorated, 0U, NSBoldFontMask));
236 // Test that the matched run gets bold-faced, but keeps the same color.
237 TEST_F(OmniboxPopupViewMacTest, DecorateMatchedStringMatch) {
238 NSString* const string = @"This is a test";
240 const NSUInteger run_length_1 = 5, run_length_2 = 2, run_length_3 = 7;
241 // Make sure nobody messed up the inputs.
242 EXPECT_EQ(run_length_1 + run_length_2 + run_length_3, [string length]);
244 // Push each run onto classifications.
245 AutocompleteMatch::ACMatchClassifications classifications;
246 classifications.push_back(
247 ACMatchClassification(0, ACMatchClassification::NONE));
248 classifications.push_back(
249 ACMatchClassification(run_length_1, ACMatchClassification::MATCH));
250 classifications.push_back(
251 ACMatchClassification(run_length_1 + run_length_2,
252 ACMatchClassification::NONE));
254 NSAttributedString* decorated =
255 OmniboxPopupViewMac::DecorateMatchedString(
256 base::SysNSStringToUTF16(string), classifications,
257 color_, dim_color_, font_);
259 // Result has same characters as the input.
260 EXPECT_EQ([decorated length], [string length]);
261 EXPECT_TRUE([[decorated string] isEqualToString:string]);
263 // Our passed-in color for the entire string.
264 EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
265 NSForegroundColorAttributeName),
267 EXPECT_TRUE(RunHasColor(decorated, 0U, color_));
269 // Should have three font runs, not bold, bold, then not bold again.
270 EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
271 NSFontAttributeName), run_length_1);
272 EXPECT_FALSE(RunHasFontTrait(decorated, 0U, NSBoldFontMask));
274 EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1,
275 NSFontAttributeName), run_length_2);
276 EXPECT_TRUE(RunHasFontTrait(decorated, run_length_1, NSBoldFontMask));
278 EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1 + run_length_2,
279 NSFontAttributeName), run_length_3);
280 EXPECT_FALSE(RunHasFontTrait(decorated, run_length_1 + run_length_2,
284 // Just like DecorateMatchedStringURLMatch, this time with URL style.
285 TEST_F(OmniboxPopupViewMacTest, DecorateMatchedStringURLMatch) {
286 NSString* const string = @"http://hello.world/";
288 const NSUInteger run_length_1 = 7, run_length_2 = 5, run_length_3 = 7;
289 // Make sure nobody messed up the inputs.
290 EXPECT_EQ(run_length_1 + run_length_2 + run_length_3, [string length]);
292 // Push each run onto classifications.
293 AutocompleteMatch::ACMatchClassifications classifications;
294 classifications.push_back(
295 ACMatchClassification(0, ACMatchClassification::URL));
296 const int kURLMatch = ACMatchClassification::URL|ACMatchClassification::MATCH;
297 classifications.push_back(ACMatchClassification(run_length_1, kURLMatch));
298 classifications.push_back(
299 ACMatchClassification(run_length_1 + run_length_2,
300 ACMatchClassification::URL));
302 NSAttributedString* decorated =
303 OmniboxPopupViewMac::DecorateMatchedString(
304 base::SysNSStringToUTF16(string), classifications,
305 color_, dim_color_, font_);
307 // Result has same characters as the input.
308 EXPECT_EQ([decorated length], [string length]);
309 EXPECT_TRUE([[decorated string] isEqualToString:string]);
311 // One color for the entire string, and it's not the one we passed in.
312 EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
313 NSForegroundColorAttributeName),
315 EXPECT_FALSE(RunHasColor(decorated, 0U, color_));
317 // Should have three font runs, not bold, bold, then not bold again.
318 EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
319 NSFontAttributeName), run_length_1);
320 EXPECT_FALSE(RunHasFontTrait(decorated, 0U, NSBoldFontMask));
322 EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1,
323 NSFontAttributeName), run_length_2);
324 EXPECT_TRUE(RunHasFontTrait(decorated, run_length_1, NSBoldFontMask));
326 EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1 + run_length_2,
327 NSFontAttributeName), run_length_3);
328 EXPECT_FALSE(RunHasFontTrait(decorated, run_length_1 + run_length_2,
332 TEST_F(OmniboxPopupViewMacTest, UpdatePopupAppearance) {
333 base::scoped_nsobject<NSTextField> field(
334 [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 20)]);
335 [[test_window() contentView] addSubview:field];
337 OmniboxViewMac view(NULL, profile(), NULL, NULL);
338 MockOmniboxPopupViewMac popup_view(&view, view.model(), field);
340 popup_view.UpdatePopupAppearance();
341 EXPECT_FALSE(popup_view.IsOpen());
342 EXPECT_EQ(0, [popup_view.matrix() numberOfRows]);
344 popup_view.SetResultCount(3);
345 popup_view.UpdatePopupAppearance();
346 EXPECT_TRUE(popup_view.IsOpen());
347 EXPECT_EQ(3, [popup_view.matrix() numberOfRows]);
349 int old_height = popup_view.GetTargetBounds().height();
350 popup_view.SetResultCount(5);
351 popup_view.UpdatePopupAppearance();
352 EXPECT_GT(popup_view.GetTargetBounds().height(), old_height);
353 EXPECT_EQ(5, [popup_view.matrix() numberOfRows]);
355 popup_view.SetResultCount(0);
356 popup_view.UpdatePopupAppearance();
357 EXPECT_FALSE(popup_view.IsOpen());
358 EXPECT_EQ(0, [popup_view.matrix() numberOfRows]);