Add abhijeet.k@samsung.com to AUTHORS list.
[chromium-blink-merge.git] / components / dom_distiller / core / distiller_unittest.cc
blob31241ec3c582bbd57a5cc40701f49a61ea8183e8
1 // Copyright 2013 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.
5 #include <algorithm>
6 #include <map>
7 #include <string>
8 #include <vector>
10 #include "base/bind.h"
11 #include "base/bind_helpers.h"
12 #include "base/location.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/values.h"
17 #include "components/dom_distiller/core/article_distillation_update.h"
18 #include "components/dom_distiller/core/distiller.h"
19 #include "components/dom_distiller/core/distiller_page.h"
20 #include "components/dom_distiller/core/fake_distiller_page.h"
21 #include "components/dom_distiller/core/proto/distilled_article.pb.h"
22 #include "components/dom_distiller/core/proto/distilled_page.pb.h"
23 #include "net/url_request/url_request_context_getter.h"
24 #include "testing/gmock/include/gmock/gmock.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26 #include "third_party/dom_distiller_js/dom_distiller.pb.h"
27 #include "third_party/dom_distiller_js/dom_distiller_json_converter.h"
29 using std::vector;
30 using std::string;
31 using ::testing::Invoke;
32 using ::testing::Return;
33 using ::testing::_;
35 using dom_distiller::proto::DomDistillerOptions;
36 using dom_distiller::proto::DomDistillerResult;
37 using dom_distiller::proto::DomDistillerResult_ContentImage;
38 using dom_distiller::proto::TimingEntry;
40 namespace {
41 const char kTitle[] = "Title";
42 const char kContent[] = "Content";
43 const char kURL[] = "http://a.com/";
44 const size_t kTotalImages = 3;
45 const char* kImageURLs[kTotalImages] = {"http://a.com/img1.jpg",
46 "http://a.com/img2.jpg",
47 "./bad_url_should_fail"};
48 const char* kImageData[kTotalImages] = {"abcde", "12345", "VWXYZ"};
49 const char kDebugLog[] = "Debug Log";
51 const string GetImageName(int page_num, int image_num) {
52 return base::IntToString(page_num) + "_" + base::IntToString(image_num);
55 scoped_ptr<base::Value> CreateDistilledValueReturnedFromJS(
56 const string& title,
57 const string& content,
58 const vector<int>& image_indices,
59 const string& next_page_url,
60 const string& prev_page_url = "") {
61 DomDistillerResult result;
62 result.set_title(title);
63 result.mutable_distilled_content()->set_html(content);
64 result.mutable_pagination_info()->set_next_page(next_page_url);
65 result.mutable_pagination_info()->set_prev_page(prev_page_url);
67 for (size_t i = 0; i < image_indices.size(); ++i) {
68 DomDistillerResult_ContentImage* curr_image = result.add_content_images();
69 curr_image->set_url(kImageURLs[image_indices[i]]);
72 return dom_distiller::proto::json::DomDistillerResult::WriteToValue(result);
75 // Return the sequence in which Distiller will distill pages.
76 // Note: ignores any delays due to fetching images etc.
77 vector<int> GetPagesInSequence(int start_page_num, int num_pages) {
78 // Distiller prefers distilling past pages first. E.g. when distillation
79 // starts on page 2 then pages are distilled in the order: 2, 1, 0, 3, 4.
80 vector<int> page_nums;
81 for (int page = start_page_num; page >= 0; --page)
82 page_nums.push_back(page);
83 for (int page = start_page_num + 1; page < num_pages; ++page)
84 page_nums.push_back(page);
85 return page_nums;
88 struct MultipageDistillerData {
89 public:
90 MultipageDistillerData() {}
91 ~MultipageDistillerData() {}
92 vector<string> page_urls;
93 vector<string> content;
94 vector<vector<int> > image_ids;
95 // The Javascript values returned by mock distiller.
96 ScopedVector<base::Value> distilled_values;
98 private:
99 DISALLOW_COPY_AND_ASSIGN(MultipageDistillerData);
102 void VerifyIncrementalUpdatesMatch(
103 const MultipageDistillerData* distiller_data,
104 int num_pages_in_article,
105 const vector<dom_distiller::ArticleDistillationUpdate>& incremental_updates,
106 int start_page_num) {
107 vector<int> page_seq =
108 GetPagesInSequence(start_page_num, num_pages_in_article);
109 // Updates should contain a list of pages. Pages in an update should be in
110 // the correct ascending page order regardless of |start_page_num|.
111 // E.g. if distillation starts at page 2 of a 3 page article, the updates
112 // will be [[2], [1, 2], [1, 2, 3]]. This example assumes that image fetches
113 // do not delay distillation of a page. There can be scenarios when image
114 // fetch delays distillation of a page (E.g. 1 is delayed due to image
115 // fetches so updates can be in this order [[2], [2,3], [1,2,3]].
116 for (size_t update_count = 0; update_count < incremental_updates.size();
117 ++update_count) {
118 const dom_distiller::ArticleDistillationUpdate& update =
119 incremental_updates[update_count];
120 EXPECT_EQ(update_count + 1, update.GetPagesSize());
122 vector<int> expected_page_nums_in_update(
123 page_seq.begin(), page_seq.begin() + update.GetPagesSize());
124 std::sort(expected_page_nums_in_update.begin(),
125 expected_page_nums_in_update.end());
127 // If we already got the first page then there is no previous page.
128 EXPECT_EQ((expected_page_nums_in_update[0] != 0), update.HasPrevPage());
130 // if we already got the last page then there is no next page.
131 EXPECT_EQ(
132 (*expected_page_nums_in_update.rbegin()) != num_pages_in_article - 1,
133 update.HasNextPage());
134 for (size_t j = 0; j < update.GetPagesSize(); ++j) {
135 int actual_page_num = expected_page_nums_in_update[j];
136 EXPECT_EQ(distiller_data->page_urls[actual_page_num],
137 update.GetDistilledPage(j).url());
138 EXPECT_EQ(distiller_data->content[actual_page_num],
139 update.GetDistilledPage(j).html());
144 string GenerateNextPageUrl(const std::string& url_prefix, size_t page_num,
145 size_t pages_size) {
146 return page_num + 1 < pages_size ?
147 url_prefix + base::IntToString(page_num + 1) : "";
150 string GeneratePrevPageUrl(const std::string& url_prefix, size_t page_num) {
151 return page_num > 0 ? url_prefix + base::IntToString(page_num - 1) : "";
154 scoped_ptr<MultipageDistillerData> CreateMultipageDistillerDataWithoutImages(
155 size_t pages_size) {
156 scoped_ptr<MultipageDistillerData> result(new MultipageDistillerData());
157 string url_prefix = kURL;
158 for (size_t page_num = 0; page_num < pages_size; ++page_num) {
159 result->page_urls.push_back(url_prefix + base::IntToString(page_num));
160 result->content.push_back("Content for page:" +
161 base::IntToString(page_num));
162 result->image_ids.push_back(vector<int>());
163 string next_page_url =
164 GenerateNextPageUrl(url_prefix, page_num, pages_size);
165 string prev_page_url =
166 GeneratePrevPageUrl(url_prefix, page_num);
167 scoped_ptr<base::Value> distilled_value =
168 CreateDistilledValueReturnedFromJS(kTitle,
169 result->content[page_num],
170 result->image_ids[page_num],
171 next_page_url,
172 prev_page_url);
173 result->distilled_values.push_back(distilled_value.release());
175 return result.Pass();
178 void VerifyArticleProtoMatchesMultipageData(
179 const dom_distiller::DistilledArticleProto* article_proto,
180 const MultipageDistillerData* distiller_data,
181 size_t distilled_pages_size,
182 size_t total_pages_size) {
183 ASSERT_EQ(distilled_pages_size,
184 static_cast<size_t>(article_proto->pages_size()));
185 EXPECT_EQ(kTitle, article_proto->title());
186 std::string url_prefix = kURL;
187 for (size_t page_num = 0; page_num < distilled_pages_size; ++page_num) {
188 const dom_distiller::DistilledPageProto& page =
189 article_proto->pages(page_num);
190 EXPECT_EQ(distiller_data->content[page_num], page.html());
191 EXPECT_EQ(distiller_data->page_urls[page_num], page.url());
192 EXPECT_EQ(distiller_data->image_ids[page_num].size(),
193 static_cast<size_t>(page.image_size()));
194 const vector<int>& image_ids_for_page = distiller_data->image_ids[page_num];
195 for (size_t img_num = 0; img_num < image_ids_for_page.size(); ++img_num) {
196 EXPECT_EQ(kImageData[image_ids_for_page[img_num]],
197 page.image(img_num).data());
198 EXPECT_EQ(GetImageName(page_num + 1, img_num),
199 page.image(img_num).name());
201 std::string expected_next_page_url =
202 GenerateNextPageUrl(url_prefix, page_num, total_pages_size);
203 std::string expected_prev_page_url =
204 GeneratePrevPageUrl(url_prefix, page_num);
205 EXPECT_EQ(expected_next_page_url, page.pagination_info().next_page());
206 EXPECT_EQ(expected_prev_page_url, page.pagination_info().prev_page());
207 EXPECT_FALSE(page.pagination_info().has_canonical_page());
211 } // namespace
213 namespace dom_distiller {
215 using test::MockDistillerPage;
216 using test::MockDistillerPageFactory;
218 class TestDistillerURLFetcher : public DistillerURLFetcher {
219 public:
220 explicit TestDistillerURLFetcher(bool delay_fetch)
221 : DistillerURLFetcher(NULL), delay_fetch_(delay_fetch) {
222 responses_[kImageURLs[0]] = string(kImageData[0]);
223 responses_[kImageURLs[1]] = string(kImageData[1]);
226 void FetchURL(const string& url,
227 const URLFetcherCallback& callback) override {
228 ASSERT_FALSE(callback.is_null());
229 url_ = url;
230 callback_ = callback;
231 if (!delay_fetch_) {
232 PostCallbackTask();
236 void PostCallbackTask() {
237 ASSERT_TRUE(base::MessageLoop::current());
238 ASSERT_FALSE(callback_.is_null());
239 base::MessageLoop::current()->PostTask(
240 FROM_HERE, base::Bind(callback_, responses_[url_]));
243 private:
244 std::map<string, string> responses_;
245 string url_;
246 URLFetcherCallback callback_;
247 bool delay_fetch_;
250 class TestDistillerURLFetcherFactory : public DistillerURLFetcherFactory {
251 public:
252 TestDistillerURLFetcherFactory() : DistillerURLFetcherFactory(NULL) {}
254 ~TestDistillerURLFetcherFactory() override {}
255 DistillerURLFetcher* CreateDistillerURLFetcher() const override {
256 return new TestDistillerURLFetcher(false);
260 class MockDistillerURLFetcherFactory : public DistillerURLFetcherFactory {
261 public:
262 MockDistillerURLFetcherFactory() : DistillerURLFetcherFactory(NULL) {}
263 virtual ~MockDistillerURLFetcherFactory() {}
265 MOCK_CONST_METHOD0(CreateDistillerURLFetcher, DistillerURLFetcher*());
268 class DistillerTest : public testing::Test {
269 public:
270 ~DistillerTest() override {}
272 void OnDistillArticleDone(scoped_ptr<DistilledArticleProto> proto) {
273 article_proto_ = proto.Pass();
276 void OnDistillArticleUpdate(const ArticleDistillationUpdate& article_update) {
277 in_sequence_updates_.push_back(article_update);
280 void DistillPage(const std::string& url,
281 scoped_ptr<DistillerPage> distiller_page) {
282 distiller_->DistillPage(GURL(url),
283 distiller_page.Pass(),
284 base::Bind(&DistillerTest::OnDistillArticleDone,
285 base::Unretained(this)),
286 base::Bind(&DistillerTest::OnDistillArticleUpdate,
287 base::Unretained(this)));
290 protected:
291 scoped_ptr<DistillerImpl> distiller_;
292 scoped_ptr<DistilledArticleProto> article_proto_;
293 std::vector<ArticleDistillationUpdate> in_sequence_updates_;
294 MockDistillerPageFactory page_factory_;
295 TestDistillerURLFetcherFactory url_fetcher_factory_;
298 ACTION_P3(DistillerPageOnDistillationDone, distiller_page, url, result) {
299 distiller_page->OnDistillationDone(url, result);
302 scoped_ptr<DistillerPage> CreateMockDistillerPage(const base::Value* result,
303 const GURL& url) {
304 MockDistillerPage* distiller_page = new MockDistillerPage();
305 EXPECT_CALL(*distiller_page, DistillPageImpl(url, _))
306 .WillOnce(DistillerPageOnDistillationDone(distiller_page, url, result));
307 return scoped_ptr<DistillerPage>(distiller_page).Pass();
310 scoped_ptr<DistillerPage> CreateMockDistillerPageWithPendingJSCallback(
311 MockDistillerPage** distiller_page_ptr,
312 const GURL& url) {
313 MockDistillerPage* distiller_page = new MockDistillerPage();
314 *distiller_page_ptr = distiller_page;
315 EXPECT_CALL(*distiller_page, DistillPageImpl(url, _));
316 return scoped_ptr<DistillerPage>(distiller_page).Pass();
319 scoped_ptr<DistillerPage> CreateMockDistillerPages(
320 MultipageDistillerData* distiller_data,
321 size_t pages_size,
322 int start_page_num) {
323 MockDistillerPage* distiller_page = new MockDistillerPage();
325 testing::InSequence s;
326 vector<int> page_nums = GetPagesInSequence(start_page_num, pages_size);
327 for (size_t page_num = 0; page_num < pages_size; ++page_num) {
328 int page = page_nums[page_num];
329 GURL url = GURL(distiller_data->page_urls[page]);
330 EXPECT_CALL(*distiller_page, DistillPageImpl(url, _))
331 .WillOnce(DistillerPageOnDistillationDone(
332 distiller_page, url, distiller_data->distilled_values[page]));
335 return scoped_ptr<DistillerPage>(distiller_page).Pass();
338 TEST_F(DistillerTest, DistillPage) {
339 base::MessageLoopForUI loop;
340 scoped_ptr<base::Value> result =
341 CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), "");
342 distiller_.reset(
343 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
344 DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
345 base::MessageLoop::current()->RunUntilIdle();
346 EXPECT_EQ(kTitle, article_proto_->title());
347 ASSERT_EQ(article_proto_->pages_size(), 1);
348 const DistilledPageProto& first_page = article_proto_->pages(0);
349 EXPECT_EQ(kContent, first_page.html());
350 EXPECT_EQ(kURL, first_page.url());
353 TEST_F(DistillerTest, DistillPageWithDebugInfo) {
354 base::MessageLoopForUI loop;
355 DomDistillerResult dd_result;
356 dd_result.mutable_debug_info()->set_log(kDebugLog);
357 scoped_ptr<base::Value> result =
358 dom_distiller::proto::json::DomDistillerResult::WriteToValue(dd_result);
359 distiller_.reset(
360 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
361 DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
362 base::MessageLoop::current()->RunUntilIdle();
363 const DistilledPageProto& first_page = article_proto_->pages(0);
364 EXPECT_EQ(kDebugLog, first_page.debug_info().log());
367 void SetTimingEntry(TimingEntry* entry, const std::string& name, double time) {
368 entry->set_name(name);
369 entry->set_time(time);
372 TEST_F(DistillerTest, DistillPageWithTimingInfo) {
373 base::MessageLoopForUI loop;
374 DomDistillerResult dd_result;
375 dd_result.mutable_timing_info()->set_total_time(1.0);
376 dd_result.mutable_timing_info()->set_markup_parsing_time(2.0);
377 dd_result.mutable_timing_info()->set_document_construction_time(3.0);
378 dd_result.mutable_timing_info()->set_article_processing_time(4.0);
379 dd_result.mutable_timing_info()->set_formatting_time(5.0);
380 SetTimingEntry(
381 dd_result.mutable_timing_info()->add_other_times(), "time0", 6.0);
382 SetTimingEntry(
383 dd_result.mutable_timing_info()->add_other_times(), "time1", 7.0);
384 scoped_ptr<base::Value> result =
385 dom_distiller::proto::json::DomDistillerResult::WriteToValue(dd_result);
386 distiller_.reset(
387 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
388 DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
389 base::MessageLoop::current()->RunUntilIdle();
390 const DistilledPageProto& first_page = article_proto_->pages(0);
391 std::map<std::string, double> timings;
392 for (int i = 0; i < first_page.timing_info_size(); ++i) {
393 DistilledPageProto::TimingInfo timing = first_page.timing_info(i);
394 timings[timing.name()] = timing.time();
396 EXPECT_EQ(7u, timings.size());
397 EXPECT_EQ(1.0, timings["total"]);
398 EXPECT_EQ(2.0, timings["markup_parsing"]);
399 EXPECT_EQ(3.0, timings["document_construction"]);
400 EXPECT_EQ(4.0, timings["article_processing"]);
401 EXPECT_EQ(5.0, timings["formatting"]);
402 EXPECT_EQ(6.0, timings["time0"]);
403 EXPECT_EQ(7.0, timings["time1"]);
406 TEST_F(DistillerTest, DistillPageWithImages) {
407 base::MessageLoopForUI loop;
408 vector<int> image_indices;
409 image_indices.push_back(0);
410 image_indices.push_back(1);
411 image_indices.push_back(2);
412 scoped_ptr<base::Value> result =
413 CreateDistilledValueReturnedFromJS(kTitle, kContent, image_indices, "");
414 distiller_.reset(
415 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
416 DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
417 base::MessageLoop::current()->RunUntilIdle();
418 EXPECT_EQ(kTitle, article_proto_->title());
419 ASSERT_EQ(article_proto_->pages_size(), 1);
420 const DistilledPageProto& first_page = article_proto_->pages(0);
421 EXPECT_EQ(kContent, first_page.html());
422 EXPECT_EQ(kURL, first_page.url());
423 ASSERT_EQ(2, first_page.image_size());
424 EXPECT_EQ(kImageData[0], first_page.image(0).data());
425 EXPECT_EQ(kImageURLs[0], first_page.image(0).url());
426 EXPECT_EQ(GetImageName(1, 0), first_page.image(0).name());
427 EXPECT_EQ(kImageData[1], first_page.image(1).data());
428 EXPECT_EQ(kImageURLs[1], first_page.image(1).url());
429 EXPECT_EQ(GetImageName(1, 1), first_page.image(1).name());
432 TEST_F(DistillerTest, DistillMultiplePages) {
433 base::MessageLoopForUI loop;
434 const size_t kNumPages = 8;
435 scoped_ptr<MultipageDistillerData> distiller_data =
436 CreateMultipageDistillerDataWithoutImages(kNumPages);
438 // Add images.
439 int next_image_number = 0;
440 for (size_t page_num = 0; page_num < kNumPages; ++page_num) {
441 // Each page has different number of images.
442 size_t tot_images = (page_num + kTotalImages) % (kTotalImages + 1);
443 vector<int> image_indices;
444 for (size_t img_num = 0; img_num < tot_images; img_num++) {
445 image_indices.push_back(next_image_number);
446 next_image_number = (next_image_number + 1) % kTotalImages;
448 distiller_data->image_ids.push_back(image_indices);
451 distiller_.reset(
452 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
453 DistillPage(
454 distiller_data->page_urls[0],
455 CreateMockDistillerPages(distiller_data.get(), kNumPages, 0).Pass());
456 base::MessageLoop::current()->RunUntilIdle();
457 VerifyArticleProtoMatchesMultipageData(
458 article_proto_.get(), distiller_data.get(), kNumPages, kNumPages);
461 TEST_F(DistillerTest, DistillLinkLoop) {
462 base::MessageLoopForUI loop;
463 // Create a loop, the next page is same as the current page. This could
464 // happen if javascript misparses a next page link.
465 scoped_ptr<base::Value> result =
466 CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), kURL);
467 distiller_.reset(
468 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
469 DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
470 base::MessageLoop::current()->RunUntilIdle();
471 EXPECT_EQ(kTitle, article_proto_->title());
472 EXPECT_EQ(article_proto_->pages_size(), 1);
475 TEST_F(DistillerTest, CheckMaxPageLimitExtraPage) {
476 base::MessageLoopForUI loop;
477 const size_t kMaxPagesInArticle = 10;
478 scoped_ptr<MultipageDistillerData> distiller_data =
479 CreateMultipageDistillerDataWithoutImages(kMaxPagesInArticle);
481 // Note: Next page url of the last page of article is set. So distiller will
482 // try to do kMaxPagesInArticle + 1 calls if the max article limit does not
483 // work.
484 scoped_ptr<base::Value> last_page_data =
485 CreateDistilledValueReturnedFromJS(
486 kTitle,
487 distiller_data->content[kMaxPagesInArticle - 1],
488 vector<int>(),
490 distiller_data->page_urls[kMaxPagesInArticle - 2]);
492 distiller_data->distilled_values.pop_back();
493 distiller_data->distilled_values.push_back(last_page_data.release());
495 distiller_.reset(
496 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
498 distiller_->SetMaxNumPagesInArticle(kMaxPagesInArticle);
500 DistillPage(distiller_data->page_urls[0],
501 CreateMockDistillerPages(
502 distiller_data.get(), kMaxPagesInArticle, 0).Pass());
503 base::MessageLoop::current()->RunUntilIdle();
504 EXPECT_EQ(kTitle, article_proto_->title());
505 EXPECT_EQ(kMaxPagesInArticle,
506 static_cast<size_t>(article_proto_->pages_size()));
509 TEST_F(DistillerTest, CheckMaxPageLimitExactLimit) {
510 base::MessageLoopForUI loop;
511 const size_t kMaxPagesInArticle = 10;
512 scoped_ptr<MultipageDistillerData> distiller_data =
513 CreateMultipageDistillerDataWithoutImages(kMaxPagesInArticle);
515 distiller_.reset(
516 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
518 // Check if distilling an article with exactly the page limit works.
519 distiller_->SetMaxNumPagesInArticle(kMaxPagesInArticle);
521 DistillPage(distiller_data->page_urls[0],
522 CreateMockDistillerPages(
523 distiller_data.get(), kMaxPagesInArticle, 0).Pass());
524 base::MessageLoop::current()->RunUntilIdle();
525 EXPECT_EQ(kTitle, article_proto_->title());
526 EXPECT_EQ(kMaxPagesInArticle,
527 static_cast<size_t>(article_proto_->pages_size()));
530 TEST_F(DistillerTest, SinglePageDistillationFailure) {
531 base::MessageLoopForUI loop;
532 // To simulate failure return a null value.
533 scoped_ptr<base::Value> nullValue(base::Value::CreateNullValue());
534 distiller_.reset(
535 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
536 DistillPage(kURL,
537 CreateMockDistillerPage(nullValue.get(), GURL(kURL)).Pass());
538 base::MessageLoop::current()->RunUntilIdle();
539 EXPECT_EQ("", article_proto_->title());
540 EXPECT_EQ(0, article_proto_->pages_size());
543 TEST_F(DistillerTest, MultiplePagesDistillationFailure) {
544 base::MessageLoopForUI loop;
545 const size_t kNumPages = 8;
546 scoped_ptr<MultipageDistillerData> distiller_data =
547 CreateMultipageDistillerDataWithoutImages(kNumPages);
549 // The page number of the failed page.
550 size_t failed_page_num = 3;
551 // reset distilled data of the failed page.
552 distiller_data->distilled_values.erase(
553 distiller_data->distilled_values.begin() + failed_page_num);
554 distiller_data->distilled_values.insert(
555 distiller_data->distilled_values.begin() + failed_page_num,
556 base::Value::CreateNullValue());
557 // Expect only calls till the failed page number.
558 distiller_.reset(
559 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
560 DistillPage(distiller_data->page_urls[0],
561 CreateMockDistillerPages(
562 distiller_data.get(), failed_page_num + 1, 0).Pass());
563 base::MessageLoop::current()->RunUntilIdle();
564 EXPECT_EQ(kTitle, article_proto_->title());
565 VerifyArticleProtoMatchesMultipageData(
566 article_proto_.get(), distiller_data.get(), failed_page_num, kNumPages);
569 TEST_F(DistillerTest, DistillPreviousPage) {
570 base::MessageLoopForUI loop;
571 const size_t kNumPages = 8;
573 // The page number of the article on which distillation starts.
574 int start_page_num = 3;
575 scoped_ptr<MultipageDistillerData> distiller_data =
576 CreateMultipageDistillerDataWithoutImages(kNumPages);
578 distiller_.reset(
579 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
580 DistillPage(distiller_data->page_urls[start_page_num],
581 CreateMockDistillerPages(
582 distiller_data.get(), kNumPages, start_page_num).Pass());
583 base::MessageLoop::current()->RunUntilIdle();
584 VerifyArticleProtoMatchesMultipageData(
585 article_proto_.get(), distiller_data.get(), kNumPages, kNumPages);
588 TEST_F(DistillerTest, IncrementalUpdates) {
589 base::MessageLoopForUI loop;
590 const size_t kNumPages = 8;
592 // The page number of the article on which distillation starts.
593 int start_page_num = 3;
594 scoped_ptr<MultipageDistillerData> distiller_data =
595 CreateMultipageDistillerDataWithoutImages(kNumPages);
597 distiller_.reset(
598 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
599 DistillPage(distiller_data->page_urls[start_page_num],
600 CreateMockDistillerPages(
601 distiller_data.get(), kNumPages, start_page_num).Pass());
602 base::MessageLoop::current()->RunUntilIdle();
603 EXPECT_EQ(kTitle, article_proto_->title());
604 ASSERT_EQ(kNumPages, static_cast<size_t>(article_proto_->pages_size()));
605 EXPECT_EQ(kNumPages, in_sequence_updates_.size());
607 VerifyIncrementalUpdatesMatch(
608 distiller_data.get(), kNumPages, in_sequence_updates_, start_page_num);
611 TEST_F(DistillerTest, IncrementalUpdatesDoNotDeleteFinalArticle) {
612 base::MessageLoopForUI loop;
613 const size_t kNumPages = 8;
614 int start_page_num = 3;
615 scoped_ptr<MultipageDistillerData> distiller_data =
616 CreateMultipageDistillerDataWithoutImages(kNumPages);
618 distiller_.reset(
619 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
620 DistillPage(distiller_data->page_urls[start_page_num],
621 CreateMockDistillerPages(
622 distiller_data.get(), kNumPages, start_page_num).Pass());
623 base::MessageLoop::current()->RunUntilIdle();
624 EXPECT_EQ(kNumPages, in_sequence_updates_.size());
626 in_sequence_updates_.clear();
628 // Should still be able to access article and pages.
629 VerifyArticleProtoMatchesMultipageData(
630 article_proto_.get(), distiller_data.get(), kNumPages, kNumPages);
633 TEST_F(DistillerTest, DeletingArticleDoesNotInterfereWithUpdates) {
634 base::MessageLoopForUI loop;
635 const size_t kNumPages = 8;
636 scoped_ptr<MultipageDistillerData> distiller_data =
637 CreateMultipageDistillerDataWithoutImages(kNumPages);
638 // The page number of the article on which distillation starts.
639 int start_page_num = 3;
641 distiller_.reset(
642 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
643 DistillPage(distiller_data->page_urls[start_page_num],
644 CreateMockDistillerPages(
645 distiller_data.get(), kNumPages, start_page_num).Pass());
646 base::MessageLoop::current()->RunUntilIdle();
647 EXPECT_EQ(kNumPages, in_sequence_updates_.size());
648 EXPECT_EQ(kTitle, article_proto_->title());
649 ASSERT_EQ(kNumPages, static_cast<size_t>(article_proto_->pages_size()));
651 // Delete the article.
652 article_proto_.reset();
653 VerifyIncrementalUpdatesMatch(
654 distiller_data.get(), kNumPages, in_sequence_updates_, start_page_num);
657 TEST_F(DistillerTest, CancelWithDelayedImageFetchCallback) {
658 base::MessageLoopForUI loop;
659 vector<int> image_indices;
660 image_indices.push_back(0);
661 scoped_ptr<base::Value> distilled_value =
662 CreateDistilledValueReturnedFromJS(kTitle, kContent, image_indices, "");
663 TestDistillerURLFetcher* delayed_fetcher = new TestDistillerURLFetcher(true);
664 MockDistillerURLFetcherFactory mock_url_fetcher_factory;
665 EXPECT_CALL(mock_url_fetcher_factory, CreateDistillerURLFetcher())
666 .WillOnce(Return(delayed_fetcher));
667 distiller_.reset(
668 new DistillerImpl(mock_url_fetcher_factory, DomDistillerOptions()));
669 DistillPage(
670 kURL, CreateMockDistillerPage(distilled_value.get(), GURL(kURL)).Pass());
671 base::MessageLoop::current()->RunUntilIdle();
673 // Post callback from the url fetcher and then delete the distiller.
674 delayed_fetcher->PostCallbackTask();
675 distiller_.reset();
677 base::MessageLoop::current()->RunUntilIdle();
680 TEST_F(DistillerTest, CancelWithDelayedJSCallback) {
681 base::MessageLoopForUI loop;
682 scoped_ptr<base::Value> distilled_value =
683 CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), "");
684 MockDistillerPage* distiller_page = NULL;
685 distiller_.reset(
686 new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
687 DistillPage(kURL,
688 CreateMockDistillerPageWithPendingJSCallback(&distiller_page,
689 GURL(kURL)));
690 base::MessageLoop::current()->RunUntilIdle();
692 ASSERT_TRUE(distiller_page);
693 // Post the task to execute javascript and then delete the distiller.
694 distiller_page->OnDistillationDone(GURL(kURL), distilled_value.get());
695 distiller_.reset();
697 base::MessageLoop::current()->RunUntilIdle();
700 } // namespace dom_distiller