// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/autocomplete/autocomplete_popup_view_gtk.h"
#include <gtk/gtk.h>
#include "base/utf_string_conversions.h"
#include "chrome/browser/autocomplete/autocomplete.h"
#include "chrome/browser/autocomplete/autocomplete_match.h"
#include "chrome/browser/ui/gtk/gtk_util.h"
#include "testing/platform_test.h"
namespace {
static const float kLargeWidth = 10000;
const GdkColor kContentTextColor = GDK_COLOR_RGB(0x00, 0x00, 0x00);
const GdkColor kDimContentTextColor = GDK_COLOR_RGB(0x80, 0x80, 0x80);
const GdkColor kURLTextColor = GDK_COLOR_RGB(0x00, 0x88, 0x00);
} // namespace
class AutocompletePopupViewGtkTest : public PlatformTest {
public:
AutocompletePopupViewGtkTest() { }
virtual void SetUp() {
PlatformTest::SetUp();
window_ = gtk_window_new(GTK_WINDOW_POPUP);
layout_ = gtk_widget_create_pango_layout(window_, NULL);
}
virtual void TearDown() {
g_object_unref(layout_);
gtk_widget_destroy(window_);
PlatformTest::TearDown();
}
// The google C++ Testing Framework documentation suggests making
// accessors in the fixture so that each test doesn't need to be a
// friend of the class being tested. This method just proxies the
// call through after adding the fixture's layout_.
void SetupLayoutForMatch(
const string16& text,
const AutocompleteMatch::ACMatchClassifications& classifications,
const GdkColor* base_color,
const GdkColor* dim_color,
const GdkColor* url_color,
const std::string& prefix_text) {
AutocompletePopupViewGtk::SetupLayoutForMatch(layout_,
text,
classifications,
base_color,
dim_color,
url_color,
prefix_text);
}
struct RunInfo {
PangoAttribute* attr_;
guint length_;
RunInfo() : attr_(NULL), length_(0) { }
};
RunInfo RunInfoForAttrType(guint location,
guint end_location,
PangoAttrType type) {
RunInfo retval;
PangoAttrList* attrs = pango_layout_get_attributes(layout_);
if (!attrs)
return retval;
PangoAttrIterator* attr_iter = pango_attr_list_get_iterator(attrs);
if (!attr_iter)
return retval;
for (gboolean more = true, findNextStart = false;
more;
more = pango_attr_iterator_next(attr_iter)) {
PangoAttribute* attr = pango_attr_iterator_get(attr_iter, type);
// This iterator segment doesn't have any elements of the
// desired type; keep looking.
if (!attr)
continue;
// Skip attribute ranges before the desired start point.
if (attr->end_index <= location)
continue;
// If the matching type went past the iterator segment, then set
// the length to the next start - location.
if (findNextStart) {
// If the start is still less than the location, then reset
// the match. Otherwise, check that the new attribute is, in
// fact different before shortening the run length.
if (attr->start_index <= location) {
findNextStart = false;
} else if (!pango_attribute_equal(retval.attr_, attr)) {
retval.length_ = attr->start_index - location;
break;
}
}
gint start_range, end_range;
pango_attr_iterator_range(attr_iter,
&start_range,
&end_range);
// Now we have a match. May need to keep going to shorten
// length if we reach a new item of the same type.
retval.attr_ = attr;
if (attr->end_index > (guint)end_range) {
retval.length_ = end_location - location;
findNextStart = true;
} else {
retval.length_ = attr->end_index - location;
break;
}
}
pango_attr_iterator_destroy(attr_iter);
return retval;
}
guint RunLengthForAttrType(guint location,
guint end_location,
PangoAttrType type) {
RunInfo info = RunInfoForAttrType(location,
end_location,
type);
return info.length_;
}
gboolean RunHasAttribute(guint location,
guint end_location,
PangoAttribute* attribute) {
RunInfo info = RunInfoForAttrType(location,
end_location,
attribute->klass->type);
return info.attr_ && pango_attribute_equal(info.attr_, attribute);
}
gboolean RunHasColor(guint location,
guint end_location,
const GdkColor& color) {
PangoAttribute* attribute =
pango_attr_foreground_new(color.red,
color.green,
color.blue);
gboolean retval = RunHasAttribute(location,
end_location,
attribute);
pango_attribute_destroy(attribute);
return retval;
}
gboolean RunHasWeight(guint location,
guint end_location,
PangoWeight weight) {
PangoAttribute* attribute = pango_attr_weight_new(weight);
gboolean retval = RunHasAttribute(location,
end_location,
attribute);
pango_attribute_destroy(attribute);
return retval;
}
GtkWidget* window_;
PangoLayout* layout_;
private:
DISALLOW_COPY_AND_ASSIGN(AutocompletePopupViewGtkTest);
};
// Simple inputs with no matches should result in styled output who's
// text matches the input string, with the passed-in color, and
// nothing bolded.
TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringNoMatch) {
const string16 kContents = ASCIIToUTF16("This is a test");
AutocompleteMatch::ACMatchClassifications classifications;
SetupLayoutForMatch(kContents,
classifications,
&kContentTextColor,
&kDimContentTextColor,
&kURLTextColor,
std::string());
EXPECT_EQ(kContents.size(),
RunLengthForAttrType(0U,
kContents.size(),
PANGO_ATTR_FOREGROUND));
EXPECT_TRUE(RunHasColor(0U,
kContents.size(),
kContentTextColor));
// This part's a little wacky - either we don't have a weight, or
// the weight run is the entire string and is NORMAL
guint weightLength = RunLengthForAttrType(0U,
kContents.size(),
PANGO_ATTR_WEIGHT);
if (weightLength) {
EXPECT_EQ(kContents.size(), weightLength);
EXPECT_TRUE(RunHasWeight(0U,
kContents.size(),
PANGO_WEIGHT_NORMAL));
}
}
// Identical to DecorateMatchedStringNoMatch, except test that URL
// style gets a different color than we passed in.
TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringURLNoMatch) {
const string16 kContents = ASCIIToUTF16("This is a test");
AutocompleteMatch::ACMatchClassifications classifications;
classifications.push_back(
ACMatchClassification(0U, ACMatchClassification::URL));
SetupLayoutForMatch(kContents,
classifications,
&kContentTextColor,
&kDimContentTextColor,
&kURLTextColor,
std::string());
EXPECT_EQ(kContents.size(),
RunLengthForAttrType(0U,
kContents.size(),
PANGO_ATTR_FOREGROUND));
EXPECT_TRUE(RunHasColor(0U,
kContents.size(),
kURLTextColor));
// This part's a little wacky - either we don't have a weight, or
// the weight run is the entire string and is NORMAL
guint weightLength = RunLengthForAttrType(0U,
kContents.size(),
PANGO_ATTR_WEIGHT);
if (weightLength) {
EXPECT_EQ(kContents.size(), weightLength);
EXPECT_TRUE(RunHasWeight(0U,
kContents.size(),
PANGO_WEIGHT_NORMAL));
}
}
// Test that DIM works as expected.
TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringDimNoMatch) {
const string16 kContents = ASCIIToUTF16("This is a test");
// Dim "is".
const guint runLength1 = 5, runLength2 = 2, runLength3 = 7;
// Make sure nobody messed up the inputs.
EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size());
// Push each run onto classifications.
AutocompleteMatch::ACMatchClassifications classifications;
classifications.push_back(
ACMatchClassification(0U, ACMatchClassification::NONE));
classifications.push_back(
ACMatchClassification(runLength1, ACMatchClassification::DIM));
classifications.push_back(
ACMatchClassification(runLength1 + runLength2,
ACMatchClassification::NONE));
SetupLayoutForMatch(kContents,
classifications,
&kContentTextColor,
&kDimContentTextColor,
&kURLTextColor,
std::string());
// Check the runs have expected color and length.
EXPECT_EQ(runLength1,
RunLengthForAttrType(0U,
kContents.size(),
PANGO_ATTR_FOREGROUND));
EXPECT_TRUE(RunHasColor(0U,
kContents.size(),
kContentTextColor));
EXPECT_EQ(runLength2,
RunLengthForAttrType(runLength1,
kContents.size(),
PANGO_ATTR_FOREGROUND));
EXPECT_TRUE(RunHasColor(runLength1,
kContents.size(),
kDimContentTextColor));
EXPECT_EQ(runLength3,
RunLengthForAttrType(runLength1 + runLength2,
kContents.size(),
PANGO_ATTR_FOREGROUND));
EXPECT_TRUE(RunHasColor(runLength1 + runLength2,
kContents.size(),
kContentTextColor));
// This part's a little wacky - either we don't have a weight, or
// the weight run is the entire string and is NORMAL
guint weightLength = RunLengthForAttrType(0U,
kContents.size(),
PANGO_ATTR_WEIGHT);
if (weightLength) {
EXPECT_EQ(kContents.size(), weightLength);
EXPECT_TRUE(RunHasWeight(0U,
kContents.size(),
PANGO_WEIGHT_NORMAL));
}
}
// Test that the matched run gets bold-faced, but keeps the same
// color.
TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringMatch) {
const string16 kContents = ASCIIToUTF16("This is a test");
// Match "is".
const guint runLength1 = 5, runLength2 = 2, runLength3 = 7;
// Make sure nobody messed up the inputs.
EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size());
// Push each run onto classifications.
AutocompleteMatch::ACMatchClassifications classifications;
classifications.push_back(
ACMatchClassification(0U, ACMatchClassification::NONE));
classifications.push_back(
ACMatchClassification(runLength1, ACMatchClassification::MATCH));
classifications.push_back(
ACMatchClassification(runLength1 + runLength2,
ACMatchClassification::NONE));
SetupLayoutForMatch(kContents,
classifications,
&kContentTextColor,
&kDimContentTextColor,
&kURLTextColor,
std::string());
// Check the runs have expected weight and length.
EXPECT_EQ(runLength1,
RunLengthForAttrType(0U,
kContents.size(),
PANGO_ATTR_WEIGHT));
EXPECT_TRUE(RunHasWeight(0U,
kContents.size(),
PANGO_WEIGHT_NORMAL));
EXPECT_EQ(runLength2,
RunLengthForAttrType(runLength1,
kContents.size(),
PANGO_ATTR_WEIGHT));
EXPECT_TRUE(RunHasWeight(runLength1,
kContents.size(),
PANGO_WEIGHT_BOLD));
EXPECT_EQ(runLength3,
RunLengthForAttrType(runLength1 + runLength2,
kContents.size(),
PANGO_ATTR_WEIGHT));
EXPECT_TRUE(RunHasWeight(runLength1 + runLength2,
kContents.size(),
PANGO_WEIGHT_NORMAL));
// The entire string should be the same, normal color.
EXPECT_EQ(kContents.size(),
RunLengthForAttrType(0U,
kContents.size(),
PANGO_ATTR_FOREGROUND));
EXPECT_TRUE(RunHasColor(0U,
kContents.size(),
kContentTextColor));
}
// Just like DecorateMatchedStringURLMatch, this time with URL style.
TEST_F(AutocompletePopupViewGtkTest, DecorateMatchedStringURLMatch) {
const string16 kContents = ASCIIToUTF16("http://hello.world/");
// Match "hello".
const guint runLength1 = 7, runLength2 = 5, runLength3 = 7;
// Make sure nobody messed up the inputs.
EXPECT_EQ(runLength1 + runLength2 + runLength3, kContents.size());
// Push each run onto classifications.
AutocompleteMatch::ACMatchClassifications classifications;
classifications.push_back(
ACMatchClassification(0U, ACMatchClassification::URL));
const int kURLMatch =
ACMatchClassification::URL | ACMatchClassification::MATCH;
classifications.push_back(
ACMatchClassification(runLength1,
kURLMatch));
classifications.push_back(
ACMatchClassification(runLength1 + runLength2,
ACMatchClassification::URL));
SetupLayoutForMatch(kContents,
classifications,
&kContentTextColor,
&kDimContentTextColor,
&kURLTextColor,
std::string());
// One color for the entire string, and it's not the one we passed
// in.
EXPECT_EQ(kContents.size(),
RunLengthForAttrType(0U,
kContents.size(),
PANGO_ATTR_FOREGROUND));
EXPECT_TRUE(RunHasColor(0U,
kContents.size(),
kURLTextColor));
}