blob: f41c36e83eb4f9f7c0652fd1e9ff861cd96912d1 [file] [log] [blame]
// Copyright (c) 2012 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.
#import "chrome/browser/ui/cocoa/omnibox/omnibox_popup_view_mac.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/cocoa/cocoa_profile_test.h"
#import "chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.h"
#include "ui/gfx/text_elider.h"
namespace {
const float kLargeWidth = 10000;
// Returns the length of the run starting at |location| for which
// |attributeName| remains the same.
NSUInteger RunLengthForAttribute(NSAttributedString* string,
NSUInteger location,
NSString* attributeName) {
const NSRange full_range = NSMakeRange(0, [string length]);
NSRange range;
[string attribute:attributeName
atIndex:location longestEffectiveRange:&range inRange:full_range];
// In order to signal when the run doesn't start exactly at location, return
// a weirdo length. This causes the incorrect expectation to manifest at the
// calling location, which is more useful than an EXPECT_EQ() would be here.
if (range.location != location) {
return -1;
}
return range.length;
}
// Return true if the run starting at |location| has |color| for attribute
// NSForegroundColorAttributeName.
bool RunHasColor(NSAttributedString* string,
NSUInteger location,
NSColor* color) {
const NSRange full_range = NSMakeRange(0, [string length]);
NSRange range;
NSColor* run_color = [string attribute:NSForegroundColorAttributeName
atIndex:location
longestEffectiveRange:&range
inRange:full_range];
// According to one "Ali Ozer", you can compare objects within the same color
// space using -isEqual:. Converting color spaces seems too heavyweight for
// these tests.
// http://lists.apple.com/archives/cocoa-dev/2005/May/msg00186.html
return [run_color isEqual:color] ? true : false;
}
// Return true if the run starting at |location| has the font trait(s) in |mask|
// font in NSFontAttributeName.
bool RunHasFontTrait(NSAttributedString* string,
NSUInteger location,
NSFontTraitMask mask) {
const NSRange full_range = NSMakeRange(0, [string length]);
NSRange range;
NSFont* run_font = [string attribute:NSFontAttributeName
atIndex:location
longestEffectiveRange:&range
inRange:full_range];
NSFontManager* fontManager = [NSFontManager sharedFontManager];
if (run_font && (mask == ([fontManager traitsOfFont:run_font] & mask))) {
return true;
}
return false;
}
// AutocompleteMatch doesn't really have the right constructor for our
// needs. Fake one for us to use.
AutocompleteMatch MakeMatch(const string16& contents,
const string16& description) {
AutocompleteMatch m(NULL, 1, true, AutocompleteMatchType::URL_WHAT_YOU_TYPED);
m.contents = contents;
m.description = description;
return m;
}
class MockOmniboxPopupViewMac : public OmniboxPopupViewMac {
public:
MockOmniboxPopupViewMac(OmniboxView* omnibox_view,
OmniboxEditModel* edit_model,
NSTextField* field)
: OmniboxPopupViewMac(omnibox_view, edit_model, field) {
}
void SetResultCount(size_t count) {
ACMatches matches;
for (size_t i = 0; i < count; ++i)
matches.push_back(AutocompleteMatch());
result_.Reset();
result_.AppendMatches(matches);
}
protected:
virtual const AutocompleteResult& GetResult() const OVERRIDE {
return result_;
}
private:
AutocompleteResult result_;
};
class OmniboxPopupViewMacTest : public CocoaProfileTest {
public:
OmniboxPopupViewMacTest() {
color_ = [NSColor blackColor];
dim_color_ = [NSColor darkGrayColor];
font_ = gfx::Font(
base::SysNSStringToUTF8([[NSFont userFontOfSize:12] fontName]), 12);
}
protected:
NSColor* color_; // weak
NSColor* dim_color_; // weak
gfx::Font font_;
private:
DISALLOW_COPY_AND_ASSIGN(OmniboxPopupViewMacTest);
};
// 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(OmniboxPopupViewMacTest, DecorateMatchedStringNoMatch) {
NSString* const string = @"This is a test";
AutocompleteMatch::ACMatchClassifications classifications;
NSAttributedString* decorated =
OmniboxPopupViewMac::DecorateMatchedString(
base::SysNSStringToUTF16(string), classifications,
color_, dim_color_, font_);
// Result has same characters as the input.
EXPECT_EQ([decorated length], [string length]);
EXPECT_TRUE([[decorated string] isEqualToString:string]);
// Our passed-in color for the entire string.
EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
NSForegroundColorAttributeName),
[string length]);
EXPECT_TRUE(RunHasColor(decorated, 0U, color_));
// An unbolded font for the entire string.
EXPECT_EQ(RunLengthForAttribute(decorated, 0U, NSFontAttributeName),
[string length]);
EXPECT_FALSE(RunHasFontTrait(decorated, 0U, NSBoldFontMask));
}
// Identical to DecorateMatchedStringNoMatch, except test that URL style gets a
// different color than we passed in.
TEST_F(OmniboxPopupViewMacTest, DecorateMatchedStringURLNoMatch) {
NSString* const string = @"This is a test";
AutocompleteMatch::ACMatchClassifications classifications;
classifications.push_back(
ACMatchClassification(0, ACMatchClassification::URL));
NSAttributedString* decorated =
OmniboxPopupViewMac::DecorateMatchedString(
base::SysNSStringToUTF16(string), classifications,
color_, dim_color_, font_);
// Result has same characters as the input.
EXPECT_EQ([decorated length], [string length]);
EXPECT_TRUE([[decorated string] isEqualToString:string]);
// One color for the entire string, and it's not the one we passed in.
EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
NSForegroundColorAttributeName),
[string length]);
EXPECT_FALSE(RunHasColor(decorated, 0U, color_));
// An unbolded font for the entire string.
EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
NSFontAttributeName), [string length]);
EXPECT_FALSE(RunHasFontTrait(decorated, 0U, NSBoldFontMask));
}
// Test that DIM works as expected.
TEST_F(OmniboxPopupViewMacTest, DecorateMatchedStringDimNoMatch) {
NSString* const string = @"This is a test";
// Dim "is".
const NSUInteger run_length_1 = 5, run_length_2 = 2, run_length_3 = 7;
// Make sure nobody messed up the inputs.
EXPECT_EQ(run_length_1 + run_length_2 + run_length_3, [string length]);
// Push each run onto classifications.
AutocompleteMatch::ACMatchClassifications classifications;
classifications.push_back(
ACMatchClassification(0, ACMatchClassification::NONE));
classifications.push_back(
ACMatchClassification(run_length_1, ACMatchClassification::DIM));
classifications.push_back(
ACMatchClassification(run_length_1 + run_length_2,
ACMatchClassification::NONE));
NSAttributedString* decorated =
OmniboxPopupViewMac::DecorateMatchedString(
base::SysNSStringToUTF16(string), classifications,
color_, dim_color_, font_);
// Result has same characters as the input.
EXPECT_EQ([decorated length], [string length]);
EXPECT_TRUE([[decorated string] isEqualToString:string]);
// Should have three font runs, normal, dim, normal.
EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
NSForegroundColorAttributeName),
run_length_1);
EXPECT_TRUE(RunHasColor(decorated, 0U, color_));
EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1,
NSForegroundColorAttributeName),
run_length_2);
EXPECT_TRUE(RunHasColor(decorated, run_length_1, dim_color_));
EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1 + run_length_2,
NSForegroundColorAttributeName),
run_length_3);
EXPECT_TRUE(RunHasColor(decorated, run_length_1 + run_length_2, color_));
// An unbolded font for the entire string.
EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
NSFontAttributeName), [string length]);
EXPECT_FALSE(RunHasFontTrait(decorated, 0U, NSBoldFontMask));
}
// Test that the matched run gets bold-faced, but keeps the same color.
TEST_F(OmniboxPopupViewMacTest, DecorateMatchedStringMatch) {
NSString* const string = @"This is a test";
// Match "is".
const NSUInteger run_length_1 = 5, run_length_2 = 2, run_length_3 = 7;
// Make sure nobody messed up the inputs.
EXPECT_EQ(run_length_1 + run_length_2 + run_length_3, [string length]);
// Push each run onto classifications.
AutocompleteMatch::ACMatchClassifications classifications;
classifications.push_back(
ACMatchClassification(0, ACMatchClassification::NONE));
classifications.push_back(
ACMatchClassification(run_length_1, ACMatchClassification::MATCH));
classifications.push_back(
ACMatchClassification(run_length_1 + run_length_2,
ACMatchClassification::NONE));
NSAttributedString* decorated =
OmniboxPopupViewMac::DecorateMatchedString(
base::SysNSStringToUTF16(string), classifications,
color_, dim_color_, font_);
// Result has same characters as the input.
EXPECT_EQ([decorated length], [string length]);
EXPECT_TRUE([[decorated string] isEqualToString:string]);
// Our passed-in color for the entire string.
EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
NSForegroundColorAttributeName),
[string length]);
EXPECT_TRUE(RunHasColor(decorated, 0U, color_));
// Should have three font runs, not bold, bold, then not bold again.
EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
NSFontAttributeName), run_length_1);
EXPECT_FALSE(RunHasFontTrait(decorated, 0U, NSBoldFontMask));
EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1,
NSFontAttributeName), run_length_2);
EXPECT_TRUE(RunHasFontTrait(decorated, run_length_1, NSBoldFontMask));
EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1 + run_length_2,
NSFontAttributeName), run_length_3);
EXPECT_FALSE(RunHasFontTrait(decorated, run_length_1 + run_length_2,
NSBoldFontMask));
}
// Just like DecorateMatchedStringURLMatch, this time with URL style.
TEST_F(OmniboxPopupViewMacTest, DecorateMatchedStringURLMatch) {
NSString* const string = @"http://hello.world/";
// Match "hello".
const NSUInteger run_length_1 = 7, run_length_2 = 5, run_length_3 = 7;
// Make sure nobody messed up the inputs.
EXPECT_EQ(run_length_1 + run_length_2 + run_length_3, [string length]);
// Push each run onto classifications.
AutocompleteMatch::ACMatchClassifications classifications;
classifications.push_back(
ACMatchClassification(0, ACMatchClassification::URL));
const int kURLMatch = ACMatchClassification::URL|ACMatchClassification::MATCH;
classifications.push_back(ACMatchClassification(run_length_1, kURLMatch));
classifications.push_back(
ACMatchClassification(run_length_1 + run_length_2,
ACMatchClassification::URL));
NSAttributedString* decorated =
OmniboxPopupViewMac::DecorateMatchedString(
base::SysNSStringToUTF16(string), classifications,
color_, dim_color_, font_);
// Result has same characters as the input.
EXPECT_EQ([decorated length], [string length]);
EXPECT_TRUE([[decorated string] isEqualToString:string]);
// One color for the entire string, and it's not the one we passed in.
EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
NSForegroundColorAttributeName),
[string length]);
EXPECT_FALSE(RunHasColor(decorated, 0U, color_));
// Should have three font runs, not bold, bold, then not bold again.
EXPECT_EQ(RunLengthForAttribute(decorated, 0U,
NSFontAttributeName), run_length_1);
EXPECT_FALSE(RunHasFontTrait(decorated, 0U, NSBoldFontMask));
EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1,
NSFontAttributeName), run_length_2);
EXPECT_TRUE(RunHasFontTrait(decorated, run_length_1, NSBoldFontMask));
EXPECT_EQ(RunLengthForAttribute(decorated, run_length_1 + run_length_2,
NSFontAttributeName), run_length_3);
EXPECT_FALSE(RunHasFontTrait(decorated, run_length_1 + run_length_2,
NSBoldFontMask));
}
TEST_F(OmniboxPopupViewMacTest, UpdatePopupAppearance) {
base::scoped_nsobject<NSTextField> field(
[[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 100, 20)]);
[[test_window() contentView] addSubview:field];
OmniboxViewMac view(NULL, profile(), NULL, NULL);
MockOmniboxPopupViewMac popup_view(&view, view.model(), field);
popup_view.UpdatePopupAppearance();
EXPECT_FALSE(popup_view.IsOpen());
EXPECT_EQ(0, [popup_view.matrix() numberOfRows]);
popup_view.SetResultCount(3);
popup_view.UpdatePopupAppearance();
EXPECT_TRUE(popup_view.IsOpen());
EXPECT_EQ(3, [popup_view.matrix() numberOfRows]);
int old_height = popup_view.GetTargetBounds().height();
popup_view.SetResultCount(5);
popup_view.UpdatePopupAppearance();
EXPECT_GT(popup_view.GetTargetBounds().height(), old_height);
EXPECT_EQ(5, [popup_view.matrix() numberOfRows]);
popup_view.SetResultCount(0);
popup_view.UpdatePopupAppearance();
EXPECT_FALSE(popup_view.IsOpen());
EXPECT_EQ(0, [popup_view.matrix() numberOfRows]);
}
} // namespace