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/autocomplete/contact_provider_chromeos.h" 6 7 #include <cmath> 8 #include <map> 9 #include <string> 10 #include <vector> 11 12 #include "base/memory/scoped_ptr.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/strings/string16.h" 15 #include "base/strings/string_number_conversions.h" 16 #include "base/strings/utf_string_conversions.h" 17 #include "chrome/browser/autocomplete/autocomplete_input.h" 18 #include "chrome/browser/autocomplete/autocomplete_match.h" 19 #include "chrome/browser/autocomplete/autocomplete_provider.h" 20 #include "chrome/browser/chromeos/contacts/contact.pb.h" 21 #include "chrome/browser/chromeos/contacts/contact_manager_stub.h" 22 #include "chrome/browser/chromeos/contacts/contact_test_util.h" 23 #include "chrome/test/base/testing_browser_process.h" 24 #include "chrome/test/base/testing_profile.h" 25 #include "chrome/test/base/testing_profile_manager.h" 26 #include "content/public/browser/browser_thread.h" 27 #include "content/public/test/test_browser_thread.h" 28 #include "testing/gtest/include/gtest/gtest.h" 29 30 using content::BrowserThread; 31 32 namespace { 33 34 // Initializes |contact| with the passed-in data. 35 void InitContact(const std::string& contact_id, 36 const std::string& full_name, 37 const std::string& given_name, 38 const std::string& family_name, 39 contacts::Contact* contact) { 40 contact->set_contact_id(contact_id); 41 contact->set_full_name(full_name); 42 contact->set_given_name(given_name); 43 contact->set_family_name(family_name); 44 } 45 46 } // namespace 47 48 class ContactProviderTest : public testing::Test { 49 public: 50 ContactProviderTest() : ui_thread_(BrowserThread::UI, &message_loop_) {} 51 virtual ~ContactProviderTest() {} 52 53 protected: 54 // testing::Test implementation. 55 virtual void SetUp() OVERRIDE { 56 profile_manager_.reset( 57 new TestingProfileManager(TestingBrowserProcess::GetGlobal())); 58 ASSERT_TRUE(profile_manager_->SetUp()); 59 profile_ = profile_manager_->CreateTestingProfile("test_profile"); 60 contact_manager_.reset(new contacts::ContactManagerStub(profile_)); 61 contact_provider_ = 62 new ContactProvider(NULL, profile_, contact_manager_->GetWeakPtr()); 63 } 64 65 // Starts a (synchronous) query for |utf8_text| in |contact_provider_|. 66 void StartQuery(const std::string& utf8_text) { 67 contact_provider_->Start( 68 AutocompleteInput(UTF8ToUTF16(utf8_text), 69 string16::npos, 70 string16(), 71 GURL(), 72 AutocompleteInput::INVALID_SPEC, 73 false, 74 false, 75 false, 76 AutocompleteInput::ALL_MATCHES), 77 false); // minimal_changes 78 } 79 80 // Returns the contact ID in |match|'s additional info, or an empty string if 81 // no ID is present. 82 std::string GetContactIdFromMatch(const AutocompleteMatch& match) { 83 AutocompleteMatch::AdditionalInfo::const_iterator it = 84 match.additional_info.find(ContactProvider::kMatchContactIdKey); 85 return it != match.additional_info.end() ? it->second : std::string(); 86 } 87 88 // Returns pointers to all of the Contact objects referenced in 89 // |contact_provider_|'s current results. 90 contacts::ContactPointers GetMatchedContacts() { 91 contacts::ContactPointers contacts; 92 const ACMatches& matches = contact_provider_->matches(); 93 for (size_t i = 0; i < matches.size(); ++i) { 94 const contacts::Contact* contact = contact_manager_->GetContactById( 95 profile_, GetContactIdFromMatch(matches[i])); 96 DCHECK(contact) << "Unable to find contact for match " << i; 97 contacts.push_back(contact); 98 } 99 return contacts; 100 } 101 102 // Returns a semicolon-separated string containing string representations (as 103 // provided by AutocompleteMatch::ClassificationsToString()) of the 104 // |contents_class| fields of all current matches. Results are sorted by 105 // contact ID. 106 std::string GetMatchClassifications() { 107 typedef std::map<std::string, std::string> StringMap; 108 StringMap contact_id_classifications; 109 const ACMatches& matches = contact_provider_->matches(); 110 for (size_t i = 0; i < matches.size(); ++i) { 111 std::string id = GetContactIdFromMatch(matches[i]); 112 DCHECK(!id.empty()) << "Match " << i << " lacks contact ID"; 113 contact_id_classifications[id] = AutocompleteMatch:: 114 ClassificationsToString(matches[i].contents_class); 115 } 116 117 std::string result; 118 for (StringMap::const_iterator it = contact_id_classifications.begin(); 119 it != contact_id_classifications.end(); ++it) { 120 if (!result.empty()) 121 result += ";"; 122 result += it->second; 123 } 124 return result; 125 } 126 127 base::MessageLoopForUI message_loop_; 128 content::TestBrowserThread ui_thread_; 129 130 scoped_ptr<TestingProfileManager> profile_manager_; 131 TestingProfile* profile_; 132 133 scoped_ptr<contacts::ContactManagerStub> contact_manager_; 134 scoped_refptr<ContactProvider> contact_provider_; 135 }; 136 137 TEST_F(ContactProviderTest, BasicMatching) { 138 const std::string kContactId1 = "contact_1"; 139 scoped_ptr<contacts::Contact> contact1(new contacts::Contact); 140 InitContact(kContactId1, "Bob Smith", "Bob", "Smith", contact1.get()); 141 142 const std::string kContactId2 = "contact_2"; 143 scoped_ptr<contacts::Contact> contact2(new contacts::Contact); 144 InitContact(kContactId2, "Dr. Jane Smith", "Jane", "Smith", contact2.get()); 145 146 contacts::ContactPointers contacts; 147 contacts.push_back(contact1.get()); 148 contacts.push_back(contact2.get()); 149 contact_manager_->SetContacts(contacts); 150 contact_manager_->NotifyObserversAboutUpdatedContacts(); 151 152 StartQuery("b"); 153 EXPECT_EQ( 154 contacts::test::VarContactsToString(1, contact1.get()), 155 contacts::test::ContactsToString(GetMatchedContacts())); 156 EXPECT_EQ("0,2,1,0", GetMatchClassifications()); 157 158 StartQuery("bob"); 159 EXPECT_EQ( 160 contacts::test::VarContactsToString(1, contact1.get()), 161 contacts::test::ContactsToString(GetMatchedContacts())); 162 EXPECT_EQ("0,2,3,0", GetMatchClassifications()); 163 164 StartQuery("bob smith"); 165 EXPECT_EQ( 166 contacts::test::VarContactsToString(1, contact1.get()), 167 contacts::test::ContactsToString(GetMatchedContacts())); 168 EXPECT_EQ("0,2", GetMatchClassifications()); 169 170 StartQuery("sm"); 171 EXPECT_EQ( 172 contacts::test::VarContactsToString(2, contact1.get(), contact2.get()), 173 contacts::test::ContactsToString(GetMatchedContacts())); 174 EXPECT_EQ("0,0,4,2,6,0;" "0,0,9,2,11,0", GetMatchClassifications()); 175 176 StartQuery("smith"); 177 EXPECT_EQ( 178 contacts::test::VarContactsToString(2, contact1.get(), contact2.get()), 179 contacts::test::ContactsToString(GetMatchedContacts())); 180 EXPECT_EQ("0,0,4,2;" "0,0,9,2", GetMatchClassifications()); 181 182 StartQuery("smIth BOb"); 183 EXPECT_EQ( 184 contacts::test::VarContactsToString(1, contact1.get()), 185 contacts::test::ContactsToString(GetMatchedContacts())); 186 EXPECT_EQ("0,2,3,0,4,2", GetMatchClassifications()); 187 188 StartQuery("bobo"); 189 EXPECT_EQ("", contacts::test::ContactsToString(GetMatchedContacts())); 190 EXPECT_EQ("", GetMatchClassifications()); 191 192 StartQuery("mith"); 193 EXPECT_EQ("", contacts::test::ContactsToString(GetMatchedContacts())); 194 EXPECT_EQ("", GetMatchClassifications()); 195 196 StartQuery("dr"); 197 EXPECT_EQ( 198 contacts::test::VarContactsToString(1, contact2.get()), 199 contacts::test::ContactsToString(GetMatchedContacts())); 200 EXPECT_EQ("0,2,2,0", GetMatchClassifications()); 201 202 StartQuery("dr. j"); 203 EXPECT_EQ( 204 contacts::test::VarContactsToString(1, contact2.get()), 205 contacts::test::ContactsToString(GetMatchedContacts())); 206 EXPECT_EQ("0,2,5,0", GetMatchClassifications()); 207 208 StartQuery("jane"); 209 EXPECT_EQ( 210 contacts::test::VarContactsToString(1, contact2.get()), 211 contacts::test::ContactsToString(GetMatchedContacts())); 212 EXPECT_EQ("0,0,4,2,8,0", GetMatchClassifications()); 213 } 214 215 TEST_F(ContactProviderTest, Collation) { 216 scoped_ptr<contacts::Contact> contact(new contacts::Contact); 217 InitContact("1", "Bj\xC3\xB6rn Adelsv\xC3\xA4rd", 218 "Bj\xC3\xB6rn", "Adelsv\xC3\xA4rd", 219 contact.get()); 220 221 contacts::ContactPointers contacts; 222 contacts.push_back(contact.get()); 223 contact_manager_->SetContacts(contacts); 224 contact_manager_->NotifyObserversAboutUpdatedContacts(); 225 226 StartQuery("bjorn"); 227 EXPECT_EQ( 228 contacts::test::VarContactsToString(1, contact.get()), 229 contacts::test::ContactsToString(GetMatchedContacts())); 230 EXPECT_EQ("0,2,5,0", GetMatchClassifications()); 231 232 StartQuery("adelsvard"); 233 EXPECT_EQ( 234 contacts::test::VarContactsToString(1, contact.get()), 235 contacts::test::ContactsToString(GetMatchedContacts())); 236 EXPECT_EQ("0,0,6,2", GetMatchClassifications()); 237 } 238 239 TEST_F(ContactProviderTest, Relevance) { 240 // Create more contacts than the maximum number of results that an 241 // AutocompleteProvider should return. Give them all the same family name and 242 // ascending affinities from 0.0 to 1.0. 243 const size_t kNumContacts = AutocompleteProvider::kMaxMatches + 1; 244 const std::string kFamilyName = "Jones"; 245 246 ScopedVector<contacts::Contact> contacts; 247 contacts::ContactPointers contact_pointers; 248 for (size_t i = 0; i < kNumContacts; ++i) { 249 contacts::Contact* contact = new contacts::Contact; 250 std::string id_string = base::IntToString(static_cast<int>(i)); 251 InitContact(id_string, id_string, kFamilyName, 252 id_string + " " + kFamilyName, contact); 253 contact->set_affinity(static_cast<float>(i) / kNumContacts); 254 contacts.push_back(contact); 255 contact_pointers.push_back(contact); 256 } 257 258 contact_manager_->SetContacts(contact_pointers); 259 contact_manager_->NotifyObserversAboutUpdatedContacts(); 260 261 // Do a search for the family name and check that the total number of results 262 // is limited as expected and that the results are ordered by descending 263 // affinity. 264 StartQuery(kFamilyName); 265 const ACMatches& matches = contact_provider_->matches(); 266 ASSERT_EQ(AutocompleteProvider::kMaxMatches, matches.size()); 267 268 int previous_relevance = 0; 269 for (size_t i = 0; i < matches.size(); ++i) { 270 const contacts::Contact& exp_contact = 271 *(contacts[kNumContacts - 1 - i]); 272 std::string match_id = GetContactIdFromMatch(matches[i]); 273 EXPECT_EQ(exp_contact.contact_id(), match_id) 274 << "Expected contact ID " << exp_contact.contact_id() 275 << " for match " << i << " but got " << match_id << " instead"; 276 if (i > 0) { 277 EXPECT_LE(matches[i].relevance, previous_relevance) 278 << "Match " << i << " has greater relevance than previous match"; 279 } 280 EXPECT_FALSE(matches[i].allowed_to_be_default_match); 281 previous_relevance = matches[i].relevance; 282 } 283 } 284