Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / gtk / omnibox / omnibox_popup_view_gtk_unittest.cc
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/memory/scoped_ptr.h"
10 #include "base/metrics/field_trial.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "chrome/browser/autocomplete/autocomplete_match.h"
13 #include "chrome/browser/autocomplete/autocomplete_result.h"
14 #include "components/variations/entropy_provider.h"
15 #include "testing/platform_test.h"
16 #include "ui/base/gtk/gtk_hig_constants.h"
17 #include "ui/gfx/font.h"
18 #include "ui/gfx/rect.h"
19
20 namespace {
21
22 const GdkColor kContentTextColor = GDK_COLOR_RGB(0x00, 0x00, 0x00);
23 const GdkColor kDimContentTextColor = GDK_COLOR_RGB(0x80, 0x80, 0x80);
24 const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00);
25
26 class TestableOmniboxPopupViewGtk : public OmniboxPopupViewGtk {
27  public:
28   TestableOmniboxPopupViewGtk()
29       : OmniboxPopupViewGtk(gfx::Font(), NULL, NULL, NULL),
30         show_called_(false),
31         hide_called_(false) {
32   }
33
34   virtual ~TestableOmniboxPopupViewGtk() {
35   }
36
37   virtual void Show(size_t num_results) OVERRIDE {
38     show_called_ = true;
39   }
40
41   virtual void Hide() OVERRIDE {
42     hide_called_ = true;
43   }
44
45   virtual const AutocompleteResult& GetResult() const OVERRIDE {
46     return result_;
47   }
48
49   using OmniboxPopupViewGtk::GetRectForLine;
50   using OmniboxPopupViewGtk::LineFromY;
51   using OmniboxPopupViewGtk::GetHiddenMatchCount;
52
53   AutocompleteResult result_;
54   bool show_called_;
55   bool hide_called_;
56 };
57
58 }  // namespace
59
60 class OmniboxPopupViewGtkTest : public PlatformTest {
61  public:
62   OmniboxPopupViewGtkTest() {}
63
64   virtual void SetUp() {
65     PlatformTest::SetUp();
66
67     window_ = gtk_window_new(GTK_WINDOW_POPUP);
68     layout_ = gtk_widget_create_pango_layout(window_, NULL);
69     view_.reset(new TestableOmniboxPopupViewGtk);
70     field_trial_list_.reset(new base::FieldTrialList(
71         new metrics::SHA1EntropyProvider("42")));
72   }
73
74   virtual void TearDown() {
75     g_object_unref(layout_);
76     gtk_widget_destroy(window_);
77
78     PlatformTest::TearDown();
79   }
80
81   // The google C++ Testing Framework documentation suggests making
82   // accessors in the fixture so that each test doesn't need to be a
83   // friend of the class being tested.  This method just proxies the
84   // call through after adding the fixture's layout_.
85   void SetupLayoutForMatch(
86       const base::string16& text,
87       const AutocompleteMatch::ACMatchClassifications& classifications,
88       const GdkColor* base_color,
89       const GdkColor* dim_color,
90       const GdkColor* url_color,
91       const std::string& prefix_text) {
92     OmniboxPopupViewGtk::SetupLayoutForMatch(layout_,
93                                              text,
94                                              classifications,
95                                              base_color,
96                                              dim_color,
97                                              url_color,
98                                              prefix_text);
99   }
100
101   struct RunInfo {
102     PangoAttribute* attr_;
103     guint length_;
104     RunInfo() : attr_(NULL), length_(0) { }
105   };
106
107   RunInfo RunInfoForAttrType(guint location,
108                              guint end_location,
109                              PangoAttrType type) {
110     RunInfo retval;
111
112     PangoAttrList* attrs = pango_layout_get_attributes(layout_);
113     if (!attrs)
114       return retval;
115
116     PangoAttrIterator* attr_iter = pango_attr_list_get_iterator(attrs);
117     if (!attr_iter)
118       return retval;
119
120     for (gboolean more = true, findNextStart = false;
121         more;
122         more = pango_attr_iterator_next(attr_iter)) {
123       PangoAttribute* attr = pango_attr_iterator_get(attr_iter, type);
124
125       // This iterator segment doesn't have any elements of the
126       // desired type; keep looking.
127       if (!attr)
128         continue;
129
130       // Skip attribute ranges before the desired start point.
131       if (attr->end_index <= location)
132         continue;
133
134       // If the matching type went past the iterator segment, then set
135       // the length to the next start - location.
136       if (findNextStart) {
137         // If the start is still less than the location, then reset
138         // the match.  Otherwise, check that the new attribute is, in
139         // fact different before shortening the run length.
140         if (attr->start_index <= location) {
141           findNextStart = false;
142         } else if (!pango_attribute_equal(retval.attr_, attr)) {
143           retval.length_ = attr->start_index - location;
144           break;
145         }
146       }
147
148       gint start_range, end_range;
149       pango_attr_iterator_range(attr_iter,
150                                 &start_range,
151                                 &end_range);
152
153       // Now we have a match.  May need to keep going to shorten
154       // length if we reach a new item of the same type.
155       retval.attr_ = attr;
156       if (attr->end_index > (guint)end_range) {
157         retval.length_ = end_location - location;
158         findNextStart = true;
159       } else {
160         retval.length_ = attr->end_index - location;
161         break;
162       }
163     }
164
165     pango_attr_iterator_destroy(attr_iter);
166     return retval;
167   }
168
169   guint RunLengthForAttrType(guint location,
170                              guint end_location,
171                              PangoAttrType type) {
172     RunInfo info = RunInfoForAttrType(location,
173                                       end_location,
174                                       type);
175     return info.length_;
176   }
177
178   gboolean RunHasAttribute(guint location,
179                            guint end_location,
180                            PangoAttribute* attribute) {
181     RunInfo info = RunInfoForAttrType(location,
182                                       end_location,
183                                       attribute->klass->type);
184
185     return info.attr_ && pango_attribute_equal(info.attr_, attribute);
186   }
187
188   gboolean RunHasColor(guint location,
189                        guint end_location,
190                        const GdkColor& color) {
191     PangoAttribute* attribute =
192         pango_attr_foreground_new(color.red,
193                                   color.green,
194                                   color.blue);
195
196     gboolean retval = RunHasAttribute(location,
197                                       end_location,
198                                       attribute);
199
200     pango_attribute_destroy(attribute);
201
202     return retval;
203   }
204
205   gboolean RunHasWeight(guint location,
206                         guint end_location,
207                         PangoWeight weight) {
208     PangoAttribute* attribute = pango_attr_weight_new(weight);
209
210     gboolean retval = RunHasAttribute(location,
211                                       end_location,
212                                       attribute);
213
214     pango_attribute_destroy(attribute);
215
216     return retval;
217   }
218
219   GtkWidget* window_;
220   PangoLayout* layout_;
221
222   scoped_ptr<TestableOmniboxPopupViewGtk> view_;
223   scoped_ptr<base::FieldTrialList> field_trial_list_;
224
225  private:
226   DISALLOW_COPY_AND_ASSIGN(OmniboxPopupViewGtkTest);
227 };
228
229 // Simple inputs with no matches should result in styled output who's
230 // text matches the input string, with the passed-in color, and
231 // nothing bolded.
232 TEST_F(OmniboxPopupViewGtkTest, DecorateMatchedStringNoMatch) {
233   const base::string16 kContents = base::ASCIIToUTF16("This is a test");
234
235   AutocompleteMatch::ACMatchClassifications classifications;
236
237   SetupLayoutForMatch(kContents,
238                       classifications,
239                       &kContentTextColor,
240                       &kDimContentTextColor,
241                       &kURLTextColor,
242                       std::string());
243
244   EXPECT_EQ(kContents.length(), RunLengthForAttrType(0U, kContents.length(),
245                                                      PANGO_ATTR_FOREGROUND));
246
247   EXPECT_TRUE(RunHasColor(0U, kContents.length(), kContentTextColor));
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, kContents.length(),
252                                            PANGO_ATTR_WEIGHT);
253   if (weightLength) {
254     EXPECT_EQ(kContents.length(), weightLength);
255     EXPECT_TRUE(RunHasWeight(0U, kContents.length(), PANGO_WEIGHT_NORMAL));
256   }
257 }
258
259 // Identical to DecorateMatchedStringNoMatch, except test that URL
260 // style gets a different color than we passed in.
261 TEST_F(OmniboxPopupViewGtkTest, DecorateMatchedStringURLNoMatch) {
262   const base::string16 kContents = base::ASCIIToUTF16("This is a test");
263   AutocompleteMatch::ACMatchClassifications classifications;
264
265   classifications.push_back(
266       ACMatchClassification(0U, ACMatchClassification::URL));
267
268   SetupLayoutForMatch(kContents,
269                       classifications,
270                       &kContentTextColor,
271                       &kDimContentTextColor,
272                       &kURLTextColor,
273                       std::string());
274
275   EXPECT_EQ(kContents.length(), RunLengthForAttrType(0U, kContents.length(),
276                                                      PANGO_ATTR_FOREGROUND));
277   EXPECT_TRUE(RunHasColor(0U, kContents.length(), kURLTextColor));
278
279   // This part's a little wacky - either we don't have a weight, or
280   // the weight run is the entire string and is NORMAL
281   guint weightLength = RunLengthForAttrType(0U, kContents.length(),
282                                            PANGO_ATTR_WEIGHT);
283   if (weightLength) {
284     EXPECT_EQ(kContents.length(), weightLength);
285     EXPECT_TRUE(RunHasWeight(0U, kContents.length(), PANGO_WEIGHT_NORMAL));
286   }
287 }
288
289 // Test that DIM works as expected.
290 TEST_F(OmniboxPopupViewGtkTest, DecorateMatchedStringDimNoMatch) {
291   const base::string16 kContents = base::ASCIIToUTF16("This is a test");
292   // Dim "is".
293   const guint kRunLength1 = 5, kRunLength2 = 2, kRunLength3 = 7;
294   // Make sure nobody messed up the inputs.
295   EXPECT_EQ(kRunLength1 + kRunLength2 + kRunLength3, kContents.length());
296
297   // Push each run onto classifications.
298   AutocompleteMatch::ACMatchClassifications classifications;
299   classifications.push_back(
300       ACMatchClassification(0U, ACMatchClassification::NONE));
301   classifications.push_back(
302       ACMatchClassification(kRunLength1, ACMatchClassification::DIM));
303   classifications.push_back(
304       ACMatchClassification(kRunLength1 + kRunLength2,
305                             ACMatchClassification::NONE));
306
307   SetupLayoutForMatch(kContents,
308                       classifications,
309                       &kContentTextColor,
310                       &kDimContentTextColor,
311                       &kURLTextColor,
312                       std::string());
313
314   // Check the runs have expected color and length.
315   EXPECT_EQ(kRunLength1, RunLengthForAttrType(0U, kContents.length(),
316                                               PANGO_ATTR_FOREGROUND));
317   EXPECT_TRUE(RunHasColor(0U, kContents.length(), kContentTextColor));
318   EXPECT_EQ(kRunLength2, RunLengthForAttrType(kRunLength1, kContents.length(),
319                                               PANGO_ATTR_FOREGROUND));
320   EXPECT_TRUE(RunHasColor(kRunLength1, kContents.length(),
321                           kDimContentTextColor));
322   EXPECT_EQ(kRunLength3, RunLengthForAttrType(kRunLength1 + kRunLength2,
323                                               kContents.length(),
324                                               PANGO_ATTR_FOREGROUND));
325   EXPECT_TRUE(RunHasColor(kRunLength1 + kRunLength2, kContents.length(),
326                           kContentTextColor));
327
328   // This part's a little wacky - either we don't have a weight, or
329   // the weight run is the entire string and is NORMAL
330   guint weightLength = RunLengthForAttrType(0U, kContents.length(),
331                                             PANGO_ATTR_WEIGHT);
332   if (weightLength) {
333     EXPECT_EQ(kContents.length(), weightLength);
334     EXPECT_TRUE(RunHasWeight(0U, kContents.length(), PANGO_WEIGHT_NORMAL));
335   }
336 }
337
338 // Test that the matched run gets bold-faced, but keeps the same
339 // color.
340 TEST_F(OmniboxPopupViewGtkTest, DecorateMatchedStringMatch) {
341   const base::string16 kContents = base::ASCIIToUTF16("This is a test");
342   // Match "is".
343   const guint kRunLength1 = 5, kRunLength2 = 2, kRunLength3 = 7;
344   // Make sure nobody messed up the inputs.
345   EXPECT_EQ(kRunLength1 + kRunLength2 + kRunLength3, kContents.length());
346
347   // Push each run onto classifications.
348   AutocompleteMatch::ACMatchClassifications classifications;
349   classifications.push_back(
350       ACMatchClassification(0U, ACMatchClassification::NONE));
351   classifications.push_back(
352       ACMatchClassification(kRunLength1, ACMatchClassification::MATCH));
353   classifications.push_back(
354       ACMatchClassification(kRunLength1 + kRunLength2,
355                             ACMatchClassification::NONE));
356
357   SetupLayoutForMatch(kContents,
358                       classifications,
359                       &kContentTextColor,
360                       &kDimContentTextColor,
361                       &kURLTextColor,
362                       std::string());
363
364   // Check the runs have expected weight and length.
365   EXPECT_EQ(kRunLength1, RunLengthForAttrType(0U, kContents.length(),
366                                               PANGO_ATTR_WEIGHT));
367   EXPECT_TRUE(RunHasWeight(0U, kContents.length(), PANGO_WEIGHT_NORMAL));
368   EXPECT_EQ(kRunLength2, RunLengthForAttrType(kRunLength1, kContents.length(),
369                                               PANGO_ATTR_WEIGHT));
370   EXPECT_TRUE(RunHasWeight(kRunLength1, kContents.length(), PANGO_WEIGHT_BOLD));
371   EXPECT_EQ(kRunLength3, RunLengthForAttrType(kRunLength1 + kRunLength2,
372                                               kContents.length(),
373                                               PANGO_ATTR_WEIGHT));
374   EXPECT_TRUE(RunHasWeight(kRunLength1 + kRunLength2, kContents.length(),
375                            PANGO_WEIGHT_NORMAL));
376
377   // The entire string should be the same, normal color.
378   EXPECT_EQ(kContents.length(), RunLengthForAttrType(0U, kContents.length(),
379                                                      PANGO_ATTR_FOREGROUND));
380   EXPECT_TRUE(RunHasColor(0U, kContents.length(), kContentTextColor));
381 }
382
383 // Just like DecorateMatchedStringURLMatch, this time with URL style.
384 TEST_F(OmniboxPopupViewGtkTest, DecorateMatchedStringURLMatch) {
385   const base::string16 kContents = base::ASCIIToUTF16("http://hello.world/");
386   // Match "hello".
387   const guint kRunLength1 = 7, kRunLength2 = 5, kRunLength3 = 7;
388   // Make sure nobody messed up the inputs.
389   EXPECT_EQ(kRunLength1 + kRunLength2 + kRunLength3, kContents.length());
390
391   // Push each run onto classifications.
392   AutocompleteMatch::ACMatchClassifications classifications;
393   classifications.push_back(
394       ACMatchClassification(0U, ACMatchClassification::URL));
395   const int kURLMatch =
396       ACMatchClassification::URL | ACMatchClassification::MATCH;
397   classifications.push_back(
398       ACMatchClassification(kRunLength1, kURLMatch));
399   classifications.push_back(
400       ACMatchClassification(kRunLength1 + kRunLength2,
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.length(), RunLengthForAttrType(0U, kContents.length(),
413                                                      PANGO_ATTR_FOREGROUND));
414   EXPECT_TRUE(RunHasColor(0U, kContents.length(), kURLTextColor));
415 }
416
417 // Test that the popup is not shown if there is only one hidden match.
418 TEST_F(OmniboxPopupViewGtkTest, HidesIfOnlyOneHiddenMatch) {
419   ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
420       "InstantExtended", "Group1 hide_verbatim:1"));
421   ACMatches matches;
422   AutocompleteMatch match;
423   match.destination_url = GURL("http://verbatim/");
424   match.type = AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED;
425   matches.push_back(match);
426   view_->result_.AppendMatches(matches);
427   ASSERT_TRUE(view_->result_.ShouldHideTopMatch());
428
429   // Since there is only one match which is hidden, the popup should close.
430   view_->UpdatePopupAppearance();
431   EXPECT_TRUE(view_->hide_called_);
432 }
433
434 // Test that the top match is skipped if the model indicates it should be
435 // hidden.
436 TEST_F(OmniboxPopupViewGtkTest, SkipsTopMatchIfHidden) {
437   ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
438       "InstantExtended", "Group1 hide_verbatim:1"));
439   ACMatches matches;
440   {
441     AutocompleteMatch match;
442     match.destination_url = GURL("http://verbatim/");
443     match.type = AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED;
444     matches.push_back(match);
445   }
446   {
447     AutocompleteMatch match;
448     match.destination_url = GURL("http://not-verbatim/");
449     match.type = AutocompleteMatchType::SEARCH_OTHER_ENGINE;
450     matches.push_back(match);
451   }
452   view_->result_.AppendMatches(matches);
453   ASSERT_TRUE(view_->result_.ShouldHideTopMatch());
454
455   EXPECT_EQ(1U, view_->GetHiddenMatchCount());
456   EXPECT_EQ(1U, view_->LineFromY(0));
457   gfx::Rect rect = view_->GetRectForLine(1, 100);
458   EXPECT_EQ(1, rect.y());
459 }
460
461 // Test that the top match is not skipped if the model does not indicate it
462 // should be hidden.
463 TEST_F(OmniboxPopupViewGtkTest, DoesNotSkipTopMatchIfVisible) {
464   ASSERT_TRUE(base::FieldTrialList::CreateFieldTrial(
465       "InstantExtended", "Group1 hide_verbatim:1"));
466   ACMatches matches;
467   AutocompleteMatch match;
468   match.destination_url = GURL("http://not-verbatim/");
469   match.type = AutocompleteMatchType::SEARCH_OTHER_ENGINE;
470   matches.push_back(match);
471   view_->result_.AppendMatches(matches);
472   ASSERT_FALSE(view_->result_.ShouldHideTopMatch());
473
474   EXPECT_EQ(0U, view_->GetHiddenMatchCount());
475   EXPECT_EQ(0U, view_->LineFromY(0));
476   gfx::Rect rect = view_->GetRectForLine(1, 100);
477   EXPECT_EQ(25, rect.y());
478
479   // The single match is visible so the popup should be open.
480   view_->UpdatePopupAppearance();
481   EXPECT_TRUE(view_->show_called_);
482 }