blob: 6bba9b135722309e143cb0b6e65e2c52765423c6 [file] [log] [blame]
mattreynolds5afc01692016-08-19 22:09:561// Copyright 2016 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 "components/omnibox/browser/physical_web_provider.h"
6
7#include <memory>
8#include <string>
9
10#include "base/memory/ptr_util.h"
11#include "base/strings/string_number_conversions.h"
12#include "base/strings/utf_string_conversions.h"
13#include "base/values.h"
14#include "components/metrics/proto/omnibox_event.pb.h"
15#include "components/omnibox/browser/mock_autocomplete_provider_client.h"
16#include "components/omnibox/browser/test_scheme_classifier.h"
17#include "components/physical_web/data_source/physical_web_data_source.h"
hayesjordan1098aecd2016-08-24 02:33:1018#include "components/physical_web/data_source/physical_web_listener.h"
mattreynolds5afc01692016-08-19 22:09:5619#include "grit/components_strings.h"
20#include "testing/gmock/include/gmock/gmock.h"
21#include "testing/gtest/include/gtest/gtest.h"
22#include "ui/base/l10n/l10n_util.h"
mattreynolds08c79092016-09-14 22:00:0823#include "ui/gfx/text_elider.h"
mattreynolds5afc01692016-08-19 22:09:5624#include "url/gurl.h"
25
26namespace {
27
28// A mock implementation of the Physical Web data source that allows setting
29// metadata for nearby URLs directly.
30class MockPhysicalWebDataSource : public PhysicalWebDataSource {
31 public:
32 MockPhysicalWebDataSource() : metadata_(new base::ListValue()) {}
33 ~MockPhysicalWebDataSource() override {}
34
35 void StartDiscovery(bool network_request_enabled) override {}
36 void StopDiscovery() override {}
37
38 std::unique_ptr<base::ListValue> GetMetadata() override {
39 return metadata_->CreateDeepCopy();
40 }
41
42 bool HasUnresolvedDiscoveries() override {
43 return false;
44 }
45
hayesjordan1098aecd2016-08-24 02:33:1046 void RegisterListener(PhysicalWebListener* physical_web_listener) override {}
47
48 void UnregisterListener(
49 PhysicalWebListener* physical_web_listener) override {}
50
mattreynolds5afc01692016-08-19 22:09:5651 // for testing
52 void SetMetadata(std::unique_ptr<base::ListValue> metadata) {
53 metadata_ = std::move(metadata);
54 }
55
56 private:
57 std::unique_ptr<base::ListValue> metadata_;
58};
59
60// An autocomplete provider client that embeds the mock Physical Web data
61// source.
62class FakeAutocompleteProviderClient
63 : public testing::NiceMock<MockAutocompleteProviderClient> {
64 public:
65 FakeAutocompleteProviderClient()
mattreynoldsc7e56a0e2016-09-27 00:11:4966 : physical_web_data_source_(
67 base::MakeUnique<MockPhysicalWebDataSource>()),
68 is_off_the_record_(false)
mattreynolds5afc01692016-08-19 22:09:5669 {
70 }
71
72 const AutocompleteSchemeClassifier& GetSchemeClassifier() const override {
73 return scheme_classifier_;
74 }
75
76 PhysicalWebDataSource* GetPhysicalWebDataSource() override {
77 return physical_web_data_source_.get();
78 }
79
mattreynoldsc7e56a0e2016-09-27 00:11:4980 bool IsOffTheRecord() const override {
81 return is_off_the_record_;
82 }
83
mattreynolds5afc01692016-08-19 22:09:5684 // Convenience method to avoid downcasts when accessing the mock data source.
85 MockPhysicalWebDataSource* GetMockPhysicalWebDataSource() {
86 return physical_web_data_source_.get();
87 }
88
mattreynoldsc7e56a0e2016-09-27 00:11:4989 // Allow tests to enable incognito mode.
90 void SetOffTheRecord(bool is_off_the_record) {
91 is_off_the_record_ = is_off_the_record;
92 }
93
mattreynolds5afc01692016-08-19 22:09:5694 private:
95 std::unique_ptr<MockPhysicalWebDataSource> physical_web_data_source_;
96 TestSchemeClassifier scheme_classifier_;
mattreynoldsc7e56a0e2016-09-27 00:11:4997 bool is_off_the_record_;
mattreynolds5afc01692016-08-19 22:09:5698};
99
mattreynolds08c79092016-09-14 22:00:08100} // namespace
101
mattreynolds5afc01692016-08-19 22:09:56102class PhysicalWebProviderTest : public testing::Test {
103 protected:
104 PhysicalWebProviderTest() : provider_(NULL) {}
105 ~PhysicalWebProviderTest() override {}
106
107 void SetUp() override {
108 client_.reset(new FakeAutocompleteProviderClient());
mattreynolds2b530de2016-09-02 18:20:16109 provider_ = PhysicalWebProvider::Create(client_.get(), nullptr);
mattreynolds5afc01692016-08-19 22:09:56110 }
111
112 void TearDown() override {
113 provider_ = NULL;
114 }
115
116 // Create a dummy metadata list with |metadata_count| items. Each item is
117 // populated with a unique scanned URL and page metadata.
118 static std::unique_ptr<base::ListValue> CreateMetadata(
119 size_t metadata_count) {
120 auto metadata_list = base::MakeUnique<base::ListValue>();
121 for (size_t i = 0; i < metadata_count; ++i) {
122 std::string item_id = base::SizeTToString(i);
123 std::string url = "https://example.com/" + item_id;
124 auto metadata_item = base::MakeUnique<base::DictionaryValue>();
mattreynolds8897fde2016-10-25 18:28:25125 metadata_item->SetString(kPhysicalWebScannedUrlKey, url);
126 metadata_item->SetString(kPhysicalWebResolvedUrlKey, url);
127 metadata_item->SetString(kPhysicalWebIconUrlKey, url);
128 metadata_item->SetString(kPhysicalWebTitleKey,
129 "Example title " + item_id);
130 metadata_item->SetString(kPhysicalWebDescriptionKey,
131 "Example description " + item_id);
mattreynolds5afc01692016-08-19 22:09:56132 metadata_list->Append(std::move(metadata_item));
133 }
134 return metadata_list;
135 }
136
mattreynolds2b530de2016-09-02 18:20:16137 // Construct an AutocompleteInput to represent tapping the omnibox from the
138 // new tab page.
139 static AutocompleteInput CreateInputForNTP() {
140 return AutocompleteInput(
141 base::string16(), base::string16::npos, std::string(), GURL(),
142 metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS,
143 false, false, true, true, true, TestSchemeClassifier());
144 }
145
146 // Construct an AutocompleteInput to represent tapping the omnibox with |url|
147 // as the current web page.
148 static AutocompleteInput CreateInputWithCurrentUrl(const std::string& url) {
mattreynolds08c79092016-09-14 22:00:08149 return AutocompleteInput(base::UTF8ToUTF16(url), base::string16::npos,
mattreynolds2b530de2016-09-02 18:20:16150 std::string(), GURL(url),
151 metrics::OmniboxEventProto::OTHER, false, false,
152 true, true, true, TestSchemeClassifier());
153 }
154
mattreynolds08c79092016-09-14 22:00:08155 // For a given |match|, check that the destination URL, contents string,
156 // description string, and default match state agree with the values specified
157 // in |url|, |contents|, |description|, and |allowed_to_be_default_match|.
158 static void ValidateMatch(const AutocompleteMatch& match,
159 const std::string& url,
160 const std::string& contents,
161 const std::string& description,
162 bool allowed_to_be_default_match) {
163 EXPECT_EQ(url, match.destination_url.spec());
164 EXPECT_EQ(contents, base::UTF16ToUTF8(match.contents));
165 EXPECT_EQ(description, base::UTF16ToUTF8(match.description));
166 EXPECT_EQ(allowed_to_be_default_match, match.allowed_to_be_default_match);
167 }
168
169 // Returns the contents string for an overflow item. Use |truncated_title|
170 // as the title of the first match, |match_count_without_default| as the
171 // total number of matches (not counting the default match), and
172 // |metadata_count| as the number of nearby Physical Web URLs for which we
173 // have metadata.
174 static std::string ConstructOverflowItemContents(
175 const std::string& truncated_title,
176 size_t match_count_without_default,
177 size_t metadata_count) {
178 // Don't treat the overflow item as a metadata match.
179 const size_t metadata_match_count = match_count_without_default - 1;
180 // Determine how many URLs we didn't create match items for.
181 const size_t additional_url_count = metadata_count - metadata_match_count;
182
183 // Build the contents string.
184 if (truncated_title.empty()) {
185 return l10n_util::GetPluralStringFUTF8(
186 IDS_PHYSICAL_WEB_OVERFLOW_EMPTY_TITLE, additional_url_count);
187 } else {
188 // Subtract one from the additional URL count because the first item is
189 // represented by its title.
190 std::string contents_suffix = l10n_util::GetPluralStringFUTF8(
191 IDS_PHYSICAL_WEB_OVERFLOW, additional_url_count - 1);
192 return truncated_title + " " + contents_suffix;
193 }
194 }
195
196 // Run a test case using |input| as the simulated state of the omnibox input
197 // field, |metadata_list| as the list of simulated Physical Web metadata,
198 // and |title_truncated| as the truncated title of the first match. In
199 // addition to checking the fields of the overflow item, this will also check
200 // that the total number of matches is equal to |expected_match_count| and
201 // that a default match and overflow item are only present when
202 // |should_expect_default_match| or |should_expect_overflow_item| are true.
203 // Metadata matches are not checked.
204 void OverflowItemTestCase(const AutocompleteInput& input,
205 std::unique_ptr<base::ListValue> metadata_list,
206 const std::string& title_truncated,
207 size_t expected_match_count,
208 bool should_expect_default_match,
209 bool should_expect_overflow_item) {
210 const size_t metadata_count = metadata_list->GetSize();
211
212 MockPhysicalWebDataSource* data_source =
213 client_->GetMockPhysicalWebDataSource();
214 EXPECT_TRUE(data_source);
215
216 data_source->SetMetadata(std::move(metadata_list));
217
218 provider_->Start(input, false);
219
220 const size_t match_count = provider_->matches().size();
221 EXPECT_EQ(expected_match_count, match_count);
222
223 const size_t match_count_without_default =
224 should_expect_default_match ? match_count - 1 : match_count;
225
226 if (should_expect_overflow_item) {
227 EXPECT_LT(match_count_without_default, metadata_count);
228 } else {
229 EXPECT_EQ(match_count_without_default, metadata_count);
230 }
231
232 size_t overflow_match_count = 0;
233 size_t default_match_count = 0;
234 for (const auto& match : provider_->matches()) {
235 if (match.type == AutocompleteMatchType::PHYSICAL_WEB_OVERFLOW) {
236 std::string contents = ConstructOverflowItemContents(
237 title_truncated, match_count_without_default, metadata_count);
238 ValidateMatch(
239 match, "chrome://physical-web/", contents,
240 l10n_util::GetStringUTF8(IDS_PHYSICAL_WEB_OVERFLOW_DESCRIPTION),
241 false);
242 ++overflow_match_count;
243 } else if (match.allowed_to_be_default_match) {
244 ++default_match_count;
245 }
246 }
247 EXPECT_EQ(should_expect_overflow_item ? 1U : 0U, overflow_match_count);
248 EXPECT_EQ(should_expect_default_match ? 1U : 0U, default_match_count);
249 }
250
mattreynolds5afc01692016-08-19 22:09:56251 std::unique_ptr<FakeAutocompleteProviderClient> client_;
252 scoped_refptr<PhysicalWebProvider> provider_;
253
254 private:
255 DISALLOW_COPY_AND_ASSIGN(PhysicalWebProviderTest);
256};
257
258TEST_F(PhysicalWebProviderTest, TestEmptyMetadataListCreatesNoMatches) {
259 MockPhysicalWebDataSource* data_source =
260 client_->GetMockPhysicalWebDataSource();
261 EXPECT_TRUE(data_source);
262
263 data_source->SetMetadata(CreateMetadata(0));
264
mattreynolds2b530de2016-09-02 18:20:16265 // Run the test with no text in the omnibox input to simulate NTP.
266 provider_->Start(CreateInputForNTP(), false);
267 EXPECT_TRUE(provider_->matches().empty());
mattreynolds5afc01692016-08-19 22:09:56268
mattreynolds2b530de2016-09-02 18:20:16269 // Run the test again with a URL in the omnibox input.
270 provider_->Start(CreateInputWithCurrentUrl("http://www.cnn.com"), false);
mattreynolds5afc01692016-08-19 22:09:56271 EXPECT_TRUE(provider_->matches().empty());
272}
273
274TEST_F(PhysicalWebProviderTest, TestSingleMetadataItemCreatesOneMatch) {
275 MockPhysicalWebDataSource* data_source =
276 client_->GetMockPhysicalWebDataSource();
277 EXPECT_TRUE(data_source);
278
279 // Extract the URL and title before inserting the metadata into the data
280 // source.
281 std::unique_ptr<base::ListValue> metadata_list = CreateMetadata(1);
282 base::DictionaryValue* metadata_item;
283 EXPECT_TRUE(metadata_list->GetDictionary(0, &metadata_item));
284 std::string resolved_url;
mattreynolds8897fde2016-10-25 18:28:25285 EXPECT_TRUE(metadata_item->GetString(kPhysicalWebResolvedUrlKey,
286 &resolved_url));
mattreynolds5afc01692016-08-19 22:09:56287 std::string title;
mattreynolds8897fde2016-10-25 18:28:25288 EXPECT_TRUE(metadata_item->GetString(kPhysicalWebTitleKey, &title));
mattreynolds5afc01692016-08-19 22:09:56289
290 data_source->SetMetadata(std::move(metadata_list));
291
mattreynolds2b530de2016-09-02 18:20:16292 // Run the test with no text in the omnibox input to simulate NTP.
293 provider_->Start(CreateInputForNTP(), false);
mattreynolds5afc01692016-08-19 22:09:56294
295 // Check that there is only one match item and its fields are correct.
296 EXPECT_EQ(1U, provider_->matches().size());
297 const AutocompleteMatch& metadata_match = provider_->matches().front();
298 EXPECT_EQ(AutocompleteMatchType::PHYSICAL_WEB, metadata_match.type);
mattreynolds08c79092016-09-14 22:00:08299 ValidateMatch(metadata_match, resolved_url, resolved_url, title, false);
mattreynolds2b530de2016-09-02 18:20:16300
301 // Run the test again with a URL in the omnibox input. An additional match
302 // should be added as a default match.
303 provider_->Start(CreateInputWithCurrentUrl("http://www.cnn.com"), false);
304
305 size_t metadata_match_count = 0;
306 size_t default_match_count = 0;
307 for (const auto& match : provider_->matches()) {
308 if (match.type == AutocompleteMatchType::PHYSICAL_WEB) {
mattreynolds08c79092016-09-14 22:00:08309 ValidateMatch(match, resolved_url, resolved_url, title, false);
mattreynolds2b530de2016-09-02 18:20:16310 ++metadata_match_count;
311 } else {
312 EXPECT_TRUE(match.allowed_to_be_default_match);
313 ++default_match_count;
314 }
315 }
316 EXPECT_EQ(2U, provider_->matches().size());
317 EXPECT_EQ(1U, metadata_match_count);
318 EXPECT_EQ(1U, default_match_count);
mattreynolds5afc01692016-08-19 22:09:56319}
320
mattreynolds5afc01692016-08-19 22:09:56321TEST_F(PhysicalWebProviderTest, TestNoMatchesWithUserInput) {
322 MockPhysicalWebDataSource* data_source =
323 client_->GetMockPhysicalWebDataSource();
324 EXPECT_TRUE(data_source);
325
326 data_source->SetMetadata(CreateMetadata(1));
327
328 // Construct an AutocompleteInput to simulate user input in the omnibox input
329 // field. The provider should not generate any matches.
330 std::string text("user input");
mattreynolds08c79092016-09-14 22:00:08331 const AutocompleteInput input(
332 base::UTF8ToUTF16(text), text.length(), std::string(), GURL(),
mattreynolds5afc01692016-08-19 22:09:56333 metrics::OmniboxEventProto::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS,
334 true, false, true, true, false, TestSchemeClassifier());
335 provider_->Start(input, false);
336
337 EXPECT_TRUE(provider_->matches().empty());
338}
339
mattreynolds08c79092016-09-14 22:00:08340TEST_F(PhysicalWebProviderTest, TestManyMetadataItemsCreatesOverflowItem) {
341 // Create enough metadata to guarantee an overflow item will be created.
342 const size_t metadata_count = AutocompleteProvider::kMaxMatches + 1;
343
344 // Run the test with no text in the omnibox input to simulate NTP.
345 OverflowItemTestCase(
346 CreateInputForNTP(), CreateMetadata(metadata_count), "Example title 0",
347 PhysicalWebProvider::kPhysicalWebMaxMatches, false, true);
348
349 // Run the test again with a URL in the omnibox input. An additional match
350 // should be added as a default match.
351 OverflowItemTestCase(CreateInputWithCurrentUrl("http://www.cnn.com"),
352 CreateMetadata(metadata_count), "Example title 0",
353 PhysicalWebProvider::kPhysicalWebMaxMatches + 1, true,
354 true);
355}
356
357TEST_F(PhysicalWebProviderTest, TestLongPageTitleIsTruncatedInOverflowItem) {
358 // Set a long title for the first item. The page title for this item will
359 // appear in the overflow item's content string.
360 auto metadata_list = CreateMetadata(AutocompleteProvider::kMaxMatches + 1);
361 base::DictionaryValue* metadata_item;
362 EXPECT_TRUE(metadata_list->GetDictionary(0, &metadata_item));
mattreynolds8897fde2016-10-25 18:28:25363 metadata_item->SetString(kPhysicalWebTitleKey, "Extra long example title 0");
mattreynolds08c79092016-09-14 22:00:08364
365 OverflowItemTestCase(CreateInputForNTP(), std::move(metadata_list),
366 "Extra long exa" + std::string(gfx::kEllipsis),
367 PhysicalWebProvider::kPhysicalWebMaxMatches, false,
368 true);
369}
370
371TEST_F(PhysicalWebProviderTest, TestEmptyPageTitleInOverflowItem) {
372 // Set an empty title for the first item. Because the title is empty, we will
373 // display an alternate string in the overflow item's contents.
374 auto metadata_list = CreateMetadata(AutocompleteProvider::kMaxMatches + 1);
375 base::DictionaryValue* metadata_item;
376 EXPECT_TRUE(metadata_list->GetDictionary(0, &metadata_item));
mattreynolds8897fde2016-10-25 18:28:25377 metadata_item->SetString(kPhysicalWebTitleKey, "");
mattreynolds08c79092016-09-14 22:00:08378
379 OverflowItemTestCase(CreateInputForNTP(), std::move(metadata_list), "",
380 PhysicalWebProvider::kPhysicalWebMaxMatches, false,
381 true);
382}
383
384TEST_F(PhysicalWebProviderTest, TestRTLPageTitleInOverflowItem) {
385 // Set a Hebrew title for the first item.
386 auto metadata_list = CreateMetadata(AutocompleteProvider::kMaxMatches + 1);
387 base::DictionaryValue* metadata_item;
388 EXPECT_TRUE(metadata_list->GetDictionary(0, &metadata_item));
mattreynolds8897fde2016-10-25 18:28:25389 metadata_item->SetString(kPhysicalWebTitleKey, "ויקיפדיה");
mattreynolds08c79092016-09-14 22:00:08390
391 OverflowItemTestCase(CreateInputForNTP(), std::move(metadata_list),
392 "ויקיפדיה", PhysicalWebProvider::kPhysicalWebMaxMatches,
393 false, true);
mattreynolds5afc01692016-08-19 22:09:56394}
mattreynoldsc7e56a0e2016-09-27 00:11:49395
396TEST_F(PhysicalWebProviderTest, TestNoMatchesInIncognito) {
397 // Enable incognito mode
398 client_->SetOffTheRecord(true);
399
400 MockPhysicalWebDataSource* data_source =
401 client_->GetMockPhysicalWebDataSource();
402 EXPECT_TRUE(data_source);
403
404 data_source->SetMetadata(CreateMetadata(1));
405 provider_->Start(CreateInputForNTP(), false);
406
407 EXPECT_TRUE(provider_->matches().empty());
408}