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