1// Copyright (c) 2011 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#include "chrome/browser/autocomplete/autocomplete_popup_view_gtk.h" 6 7#include <gtk/gtk.h> 8 9#include "base/utf_string_conversions.h" 10#include "chrome/browser/autocomplete/autocomplete.h" 11#include "chrome/browser/autocomplete/autocomplete_match.h" 12#include "chrome/browser/ui/gtk/gtk_util.h" 13#include "testing/platform_test.h" 14 15namespace { 16 17static const float kLargeWidth = 10000; 18 19const GdkColor kContentTextColor = GDK_COLOR_RGB(0x00, 0x00, 0x00); 20const GdkColor kDimContentTextColor = GDK_COLOR_RGB(0x80, 0x80, 0x80); 21const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00); 22 23} // namespace 24 25class AutocompletePopupViewGtkTest : public PlatformTest { 26 public: 27 AutocompletePopupViewGtkTest() { } 28 29 virtual void SetUp() { 30 PlatformTest::SetUp(); 31 32 window_ = gtk_window_new(GTK_WINDOW_POPUP); 33 layout_ = gtk_widget_create_pango_layout(window_, NULL); 34 } 35 36 virtual void TearDown() { 37 g_object_unref(layout_); 38 gtk_widget_destroy(window_); 39 40 PlatformTest::TearDown(); 41 } 42 43 // The google C++ Testing Framework documentation suggests making 44 // accessors in the fixture so that each test doesn't need to be a 45 // friend of the class being tested. This method just proxies the 46 // call through after adding the fixture's layout_. 47 void SetupLayoutForMatch( 48 const string16& text, 49 const AutocompleteMatch::ACMatchClassifications& classifications, 50 const GdkColor* base_color, 51 const GdkColor* dim_color, 52 const GdkColor* url_color, 53 const std::string& prefix_text) { 54 AutocompletePopupViewGtk::SetupLayoutForMatch(layout_, 55 text, 56 classifications, 57 base_color, 58 dim_color, 59 url_color, 60 prefix_text); 61 } 62 63 struct RunInfo { 64 PangoAttribute* attr_; 65 guint length_; 66 RunInfo() : attr_(NULL), length_(0) { } 67 }; 68 69 RunInfo RunInfoForAttrType(guint location, 70 guint end_location, 71 PangoAttrType type) { 72 RunInfo retval; 73 74 PangoAttrList* attrs = pango_layout_get_attributes(layout_); 75 if (!attrs) 76 return retval; 77 78 PangoAttrIterator* attr_iter = pango_attr_list_get_iterator(attrs); 79 if (!attr_iter) 80 return retval; 81 82 for (gboolean more = true, findNextStart = false; 83 more; 84 more = pango_attr_iterator_next(attr_iter)) { 85 PangoAttribute* attr = pango_attr_iterator_get(attr_iter, type); 86 87 // This iterator segment doesn't have any elements of the 88 // desired type; keep looking. 89 if (!attr) 90 continue; 91 92 // Skip attribute ranges before the desired start point. 93 if (attr->end_index <= location) 94 continue; 95 96 // If the matching type went past the iterator segment, then set 97 // the length to the next start - location. 98 if (findNextStart) { 99 // If the start is still less than the location, then reset 100 // the match. Otherwise, check that the new attribute is, in 101 // fact different before shortening the run length. 102 if (attr->start_index <= location) { 103 findNextStart = false; 104 } else if (!pango_attribute_equal(retval.attr_, attr)) { 105 retval.length_ = attr->start_index - location; 106 break; 107 } 108 } 109 110 gint start_range, end_range; 111 pango_attr_iterator_range(attr_iter, 112 &start_range, 113 &end_range); 114 115 // Now we have a match. May need to keep going to shorten 116 // length if we reach a new item of the same type. 117 retval.attr_ = attr; 118 if (attr->end_index > (guint)end_range) { 119 retval.length_ = end_location - location; 120 findNextStart = true; 121 } else { 122 retval.length_ = attr->end_index - location; 123 break; 124 } 125 } 126 127 pango_attr_iterator_destroy(attr_iter); 128 return retval; 129 } 130 131 guint RunLengthForAttrType(guint location, 132 guint end_location, 133 PangoAttrType type) { 134 RunInfo info = RunInfoForAttrType(location, 135 end_location, 136 type); 137 return info.length_; 138 } 139 140 gboolean RunHasAttribute(guint location, 141 guint end_location, 142 PangoAttribute* attribute) { 143 RunInfo info = RunInfoForAttrType(location, 144 end_location, 145 attribute->klass->type); 146 147 return info.attr_ && pango_attribute_equal(info.attr_, attribute); 148 } 149 150 gboolean RunHasColor(guint location, 151 guint end_location, 152 const GdkColor& color) { 153 PangoAttribute* attribute = 154 pango_attr_foreground_new(color.red, 155 color.green, 156 color.blue); 157 158 gboolean retval = RunHasAttribute(location, 159 end_location, 160 attribute); 161 162 pango_attribute_destroy(attribute); 163 164 return retval; 165 } 166 167 gboolean RunHasWeight(guint location, 168 guint end_location, 169 PangoWeight weight) { 170 PangoAttribute* attribute = pango_attr_weight_new(weight); 171 172 gboolean retval = RunHasAttribute(location, 173 end_location, 174 attribute); 175 176 pango_attribute_destroy(attribute); 177 178 return retval; 179 } 180 181 GtkWidget* window_; 182 PangoLayout* layout_; 183 184 private: 185 DISALLOW_COPY_AND_ASSIGN(AutocompletePopupViewGtkTest); 186}; 187 188// Simple inputs with no matches should result in styled output who's 189// text matches the input string, with the passed-in color, and 190// nothing bolded. 191TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringNoMatch) { 192 const string16 kContents = ASCIIToUTF16("This is a test"); 193 194 AutocompleteMatch::ACMatchClassifications classifications; 195 196 SetupLayoutForMatch(kContents, 197 classifications, 198 &kContentTextColor, 199 &kDimContentTextColor, 200 &kURLTextColor, 201 std::string()); 202 203 EXPECT_EQ(kContents.size(), 204 RunLengthForAttrType(0U, 205 kContents.size(), 206 PANGO_ATTR_FOREGROUND)); 207 208 EXPECT_TRUE(RunHasColor(0U, 209 kContents.size(), 210 kContentTextColor)); 211 212 // This part's a little wacky - either we don't have a weight, or 213 // the weight run is the entire string and is NORMAL 214 guint weightLength = RunLengthForAttrType(0U, 215 kContents.size(), 216 PANGO_ATTR_WEIGHT); 217 if (weightLength) { 218 EXPECT_EQ(kContents.size(), weightLength); 219 EXPECT_TRUE(RunHasWeight(0U, 220 kContents.size(), 221 PANGO_WEIGHT_NORMAL)); 222 } 223} 224 225// Identical to DecorateMatchedStringNoMatch, except test that URL 226// style gets a different color than we passed in. 227TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringURLNoMatch) { 228 const string16 kContents = ASCIIToUTF16("This is a test"); 229 AutocompleteMatch::ACMatchClassifications classifications; 230 231 classifications.push_back( 232 ACMatchClassification(0U, ACMatchClassification::URL)); 233 234 SetupLayoutForMatch(kContents, 235 classifications, 236 &kContentTextColor, 237 &kDimContentTextColor, 238 &kURLTextColor, 239 std::string()); 240 241 EXPECT_EQ(kContents.size(), 242 RunLengthForAttrType(0U, 243 kContents.size(), 244 PANGO_ATTR_FOREGROUND)); 245 EXPECT_TRUE(RunHasColor(0U, 246 kContents.size(), 247 kURLTextColor)); 248 249 // This part's a little wacky - either we don't have a weight, or 250 // the weight run is the entire string and is NORMAL 251 guint weightLength = RunLengthForAttrType(0U, 252 kContents.size(), 253 PANGO_ATTR_WEIGHT); 254 if (weightLength) { 255 EXPECT_EQ(kContents.size(), weightLength); 256 EXPECT_TRUE(RunHasWeight(0U, 257 kContents.size(), 258 PANGO_WEIGHT_NORMAL)); 259 } 260} 261 262// Test that DIM works as expected. 263TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringDimNoMatch) { 264 const string16 kContents = ASCIIToUTF16("This is a test"); 265 // Dim "is". 266 const guint runLength1 = 5, runLength2 = 2, runLength3 = 7; 267 // Make sure nobody messed up the inputs. 268 EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size()); 269 270 // Push each run onto classifications. 271 AutocompleteMatch::ACMatchClassifications classifications; 272 classifications.push_back( 273 ACMatchClassification(0U, ACMatchClassification::NONE)); 274 classifications.push_back( 275 ACMatchClassification(runLength1, ACMatchClassification::DIM)); 276 classifications.push_back( 277 ACMatchClassification(runLength1 + runLength2, 278 ACMatchClassification::NONE)); 279 280 SetupLayoutForMatch(kContents, 281 classifications, 282 &kContentTextColor, 283 &kDimContentTextColor, 284 &kURLTextColor, 285 std::string()); 286 287 // Check the runs have expected color and length. 288 EXPECT_EQ(runLength1, 289 RunLengthForAttrType(0U, 290 kContents.size(), 291 PANGO_ATTR_FOREGROUND)); 292 EXPECT_TRUE(RunHasColor(0U, 293 kContents.size(), 294 kContentTextColor)); 295 EXPECT_EQ(runLength2, 296 RunLengthForAttrType(runLength1, 297 kContents.size(), 298 PANGO_ATTR_FOREGROUND)); 299 EXPECT_TRUE(RunHasColor(runLength1, 300 kContents.size(), 301 kDimContentTextColor)); 302 EXPECT_EQ(runLength3, 303 RunLengthForAttrType(runLength1 + runLength2, 304 kContents.size(), 305 PANGO_ATTR_FOREGROUND)); 306 EXPECT_TRUE(RunHasColor(runLength1 + runLength2, 307 kContents.size(), 308 kContentTextColor)); 309 310 // This part's a little wacky - either we don't have a weight, or 311 // the weight run is the entire string and is NORMAL 312 guint weightLength = RunLengthForAttrType(0U, 313 kContents.size(), 314 PANGO_ATTR_WEIGHT); 315 if (weightLength) { 316 EXPECT_EQ(kContents.size(), weightLength); 317 EXPECT_TRUE(RunHasWeight(0U, 318 kContents.size(), 319 PANGO_WEIGHT_NORMAL)); 320 } 321} 322 323// Test that the matched run gets bold-faced, but keeps the same 324// color. 325TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringMatch) { 326 const string16 kContents = ASCIIToUTF16("This is a test"); 327 // Match "is". 328 const guint runLength1 = 5, runLength2 = 2, runLength3 = 7; 329 // Make sure nobody messed up the inputs. 330 EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size()); 331 332 // Push each run onto classifications. 333 AutocompleteMatch::ACMatchClassifications classifications; 334 classifications.push_back( 335 ACMatchClassification(0U, ACMatchClassification::NONE)); 336 classifications.push_back( 337 ACMatchClassification(runLength1, ACMatchClassification::MATCH)); 338 classifications.push_back( 339 ACMatchClassification(runLength1 + runLength2, 340 ACMatchClassification::NONE)); 341 342 SetupLayoutForMatch(kContents, 343 classifications, 344 &kContentTextColor, 345 &kDimContentTextColor, 346 &kURLTextColor, 347 std::string()); 348 349 // Check the runs have expected weight and length. 350 EXPECT_EQ(runLength1, 351 RunLengthForAttrType(0U, 352 kContents.size(), 353 PANGO_ATTR_WEIGHT)); 354 EXPECT_TRUE(RunHasWeight(0U, 355 kContents.size(), 356 PANGO_WEIGHT_NORMAL)); 357 EXPECT_EQ(runLength2, 358 RunLengthForAttrType(runLength1, 359 kContents.size(), 360 PANGO_ATTR_WEIGHT)); 361 EXPECT_TRUE(RunHasWeight(runLength1, 362 kContents.size(), 363 PANGO_WEIGHT_BOLD)); 364 EXPECT_EQ(runLength3, 365 RunLengthForAttrType(runLength1 + runLength2, 366 kContents.size(), 367 PANGO_ATTR_WEIGHT)); 368 EXPECT_TRUE(RunHasWeight(runLength1 + runLength2, 369 kContents.size(), 370 PANGO_WEIGHT_NORMAL)); 371 372 // The entire string should be the same, normal color. 373 EXPECT_EQ(kContents.size(), 374 RunLengthForAttrType(0U, 375 kContents.size(), 376 PANGO_ATTR_FOREGROUND)); 377 EXPECT_TRUE(RunHasColor(0U, 378 kContents.size(), 379 kContentTextColor)); 380} 381 382// Just like DecorateMatchedStringURLMatch, this time with URL style. 383TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringURLMatch) { 384 const string16 kContents = ASCIIToUTF16("http://hello.world/"); 385 // Match "hello". 386 const guint runLength1 = 7, runLength2 = 5, runLength3 = 7; 387 // Make sure nobody messed up the inputs. 388 EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size()); 389 390 // Push each run onto classifications. 391 AutocompleteMatch::ACMatchClassifications classifications; 392 classifications.push_back( 393 ACMatchClassification(0U, ACMatchClassification::URL)); 394 const int kURLMatch = 395 ACMatchClassification::URL | ACMatchClassification::MATCH; 396 classifications.push_back( 397 ACMatchClassification(runLength1, 398 kURLMatch)); 399 classifications.push_back( 400 ACMatchClassification(runLength1 + runLength2, 401 ACMatchClassification::URL)); 402 403 SetupLayoutForMatch(kContents, 404 classifications, 405 &kContentTextColor, 406 &kDimContentTextColor, 407 &kURLTextColor, 408 std::string()); 409 410 // One color for the entire string, and it's not the one we passed 411 // in. 412 EXPECT_EQ(kContents.size(), 413 RunLengthForAttrType(0U, 414 kContents.size(), 415 PANGO_ATTR_FOREGROUND)); 416 EXPECT_TRUE(RunHasColor(0U, 417 kContents.size(), 418 kURLTextColor)); 419} 420