Home | History | Annotate | Download | only in autocomplete
      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 
     15 namespace {
     16 
     17 static const float kLargeWidth = 10000;
     18 
     19 const GdkColor kContentTextColor = GDK_COLOR_RGB(0x00, 0x00, 0x00);
     20 const GdkColor kDimContentTextColor = GDK_COLOR_RGB(0x80, 0x80, 0x80);
     21 const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00);
     22 
     23 }  // namespace
     24 
     25 class 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.
    191 TEST_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.
    227 TEST_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.
    263 TEST_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.
    325 TEST_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.
    383 TEST_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