Upstream version 11.40.277.0
[platform/framework/web/crosswalk.git] / src / components / dom_distiller / core / distiller_unittest.cc
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.
4
5 #include <algorithm>
6 #include <map>
7 #include <string>
8 #include <vector>
9
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"
28
29 using std::vector;
30 using std::string;
31 using ::testing::Invoke;
32 using ::testing::Return;
33 using ::testing::_;
34
35 using dom_distiller::proto::DomDistillerOptions;
36 using dom_distiller::proto::DomDistillerResult;
37
38 namespace {
39 const char kTitle[] = "Title";
40 const char kContent[] = "Content";
41 const char kURL[] = "http://a.com/";
42 const size_t kTotalImages = 2;
43 const char* kImageURLs[kTotalImages] = {"http://a.com/img1.jpg",
44                                         "http://a.com/img2.jpg"};
45 const char* kImageData[kTotalImages] = {"abcde", "12345"};
46 const char kDebugLog[] = "Debug Log";
47
48 const string GetImageName(int page_num, int image_num) {
49   return base::IntToString(page_num) + "_" + base::IntToString(image_num);
50 }
51
52 scoped_ptr<base::Value> CreateDistilledValueReturnedFromJS(
53     const string& title,
54     const string& content,
55     const vector<int>& image_indices,
56     const string& next_page_url,
57     const string& prev_page_url = "") {
58   DomDistillerResult result;
59   result.set_title(title);
60   result.mutable_distilled_content()->set_html(content);
61   result.mutable_pagination_info()->set_next_page(next_page_url);
62   result.mutable_pagination_info()->set_prev_page(prev_page_url);
63
64   for (size_t i = 0; i < image_indices.size(); ++i) {
65     result.add_image_urls(kImageURLs[image_indices[i]]);
66   }
67
68   return dom_distiller::proto::json::DomDistillerResult::WriteToValue(result);
69 }
70
71 // Return the sequence in which Distiller will distill pages.
72 // Note: ignores any delays due to fetching images etc.
73 vector<int> GetPagesInSequence(int start_page_num, int num_pages) {
74   // Distiller prefers distilling past pages first. E.g. when distillation
75   // starts on page 2 then pages are distilled in the order: 2, 1, 0, 3, 4.
76   vector<int> page_nums;
77   for (int page = start_page_num; page >= 0; --page)
78     page_nums.push_back(page);
79   for (int page = start_page_num + 1; page < num_pages; ++page)
80     page_nums.push_back(page);
81   return page_nums;
82 }
83
84 struct MultipageDistillerData {
85  public:
86   MultipageDistillerData() {}
87   ~MultipageDistillerData() {}
88   vector<string> page_urls;
89   vector<string> content;
90   vector<vector<int> > image_ids;
91   // The Javascript values returned by mock distiller.
92   ScopedVector<base::Value> distilled_values;
93
94  private:
95   DISALLOW_COPY_AND_ASSIGN(MultipageDistillerData);
96 };
97
98 void VerifyIncrementalUpdatesMatch(
99     const MultipageDistillerData* distiller_data,
100     int num_pages_in_article,
101     const vector<dom_distiller::ArticleDistillationUpdate>& incremental_updates,
102     int start_page_num) {
103   vector<int> page_seq =
104       GetPagesInSequence(start_page_num, num_pages_in_article);
105   // Updates should contain a list of pages. Pages in an update should be in
106   // the correct ascending page order regardless of |start_page_num|.
107   // E.g. if distillation starts at page 2 of a 3 page article, the updates
108   // will be [[2], [1, 2], [1, 2, 3]]. This example assumes that image fetches
109   // do not delay distillation of a page. There can be scenarios when image
110   // fetch delays distillation of a page (E.g. 1 is delayed due to image
111   // fetches so updates can be in this order [[2], [2,3], [1,2,3]].
112   for (size_t update_count = 0; update_count < incremental_updates.size();
113        ++update_count) {
114     const dom_distiller::ArticleDistillationUpdate& update =
115         incremental_updates[update_count];
116     EXPECT_EQ(update_count + 1, update.GetPagesSize());
117
118     vector<int> expected_page_nums_in_update(
119         page_seq.begin(), page_seq.begin() + update.GetPagesSize());
120     std::sort(expected_page_nums_in_update.begin(),
121               expected_page_nums_in_update.end());
122
123     // If we already got the first page then there is no previous page.
124     EXPECT_EQ((expected_page_nums_in_update[0] != 0), update.HasPrevPage());
125
126     // if we already got the last page then there is no next page.
127     EXPECT_EQ(
128         (*expected_page_nums_in_update.rbegin()) != num_pages_in_article - 1,
129         update.HasNextPage());
130     for (size_t j = 0; j < update.GetPagesSize(); ++j) {
131       int actual_page_num = expected_page_nums_in_update[j];
132       EXPECT_EQ(distiller_data->page_urls[actual_page_num],
133                 update.GetDistilledPage(j).url());
134       EXPECT_EQ(distiller_data->content[actual_page_num],
135                 update.GetDistilledPage(j).html());
136     }
137   }
138 }
139
140 string GenerateNextPageUrl(const std::string& url_prefix, size_t page_num,
141     size_t pages_size) {
142   return page_num + 1 < pages_size ?
143       url_prefix + base::IntToString(page_num + 1) : "";
144 }
145
146 string GeneratePrevPageUrl(const std::string& url_prefix, size_t page_num) {
147   return page_num > 0 ? url_prefix + base::IntToString(page_num - 1) : "";
148 }
149
150 scoped_ptr<MultipageDistillerData> CreateMultipageDistillerDataWithoutImages(
151     size_t pages_size) {
152   scoped_ptr<MultipageDistillerData> result(new MultipageDistillerData());
153   string url_prefix = kURL;
154   for (size_t page_num = 0; page_num < pages_size; ++page_num) {
155     result->page_urls.push_back(url_prefix + base::IntToString(page_num));
156     result->content.push_back("Content for page:" +
157                               base::IntToString(page_num));
158     result->image_ids.push_back(vector<int>());
159     string next_page_url =
160         GenerateNextPageUrl(url_prefix, page_num, pages_size);
161     string prev_page_url =
162         GeneratePrevPageUrl(url_prefix, page_num);
163     scoped_ptr<base::Value> distilled_value =
164         CreateDistilledValueReturnedFromJS(kTitle,
165                                            result->content[page_num],
166                                            result->image_ids[page_num],
167                                            next_page_url,
168                                            prev_page_url);
169     result->distilled_values.push_back(distilled_value.release());
170   }
171   return result.Pass();
172 }
173
174 void VerifyArticleProtoMatchesMultipageData(
175     const dom_distiller::DistilledArticleProto* article_proto,
176     const MultipageDistillerData* distiller_data,
177     size_t distilled_pages_size,
178     size_t total_pages_size) {
179   ASSERT_EQ(distilled_pages_size,
180             static_cast<size_t>(article_proto->pages_size()));
181   EXPECT_EQ(kTitle, article_proto->title());
182   std::string url_prefix = kURL;
183   for (size_t page_num = 0; page_num < distilled_pages_size; ++page_num) {
184     const dom_distiller::DistilledPageProto& page =
185         article_proto->pages(page_num);
186     EXPECT_EQ(distiller_data->content[page_num], page.html());
187     EXPECT_EQ(distiller_data->page_urls[page_num], page.url());
188     EXPECT_EQ(distiller_data->image_ids[page_num].size(),
189               static_cast<size_t>(page.image_size()));
190     const vector<int>& image_ids_for_page = distiller_data->image_ids[page_num];
191     for (size_t img_num = 0; img_num < image_ids_for_page.size(); ++img_num) {
192       EXPECT_EQ(kImageData[image_ids_for_page[img_num]],
193                 page.image(img_num).data());
194       EXPECT_EQ(GetImageName(page_num + 1, img_num),
195                 page.image(img_num).name());
196     }
197     std::string expected_next_page_url =
198         GenerateNextPageUrl(url_prefix, page_num, total_pages_size);
199     std::string expected_prev_page_url =
200         GeneratePrevPageUrl(url_prefix, page_num);
201     EXPECT_EQ(expected_next_page_url, page.pagination_info().next_page());
202     EXPECT_EQ(expected_prev_page_url, page.pagination_info().prev_page());
203     EXPECT_FALSE(page.pagination_info().has_canonical_page());
204   }
205 }
206
207 }  // namespace
208
209 namespace dom_distiller {
210
211 using test::MockDistillerPage;
212 using test::MockDistillerPageFactory;
213
214 class TestDistillerURLFetcher : public DistillerURLFetcher {
215  public:
216   explicit TestDistillerURLFetcher(bool delay_fetch)
217       : DistillerURLFetcher(NULL), delay_fetch_(delay_fetch) {
218     responses_[kImageURLs[0]] = string(kImageData[0]);
219     responses_[kImageURLs[1]] = string(kImageData[1]);
220   }
221
222   void FetchURL(const string& url,
223                 const URLFetcherCallback& callback) override {
224     ASSERT_FALSE(callback.is_null());
225     url_ = url;
226     callback_ = callback;
227     if (!delay_fetch_) {
228       PostCallbackTask();
229     }
230   }
231
232   void PostCallbackTask() {
233     ASSERT_TRUE(base::MessageLoop::current());
234     ASSERT_FALSE(callback_.is_null());
235     base::MessageLoop::current()->PostTask(
236         FROM_HERE, base::Bind(callback_, responses_[url_]));
237   }
238
239  private:
240   std::map<string, string> responses_;
241   string url_;
242   URLFetcherCallback callback_;
243   bool delay_fetch_;
244 };
245
246 class TestDistillerURLFetcherFactory : public DistillerURLFetcherFactory {
247  public:
248   TestDistillerURLFetcherFactory() : DistillerURLFetcherFactory(NULL) {}
249
250   ~TestDistillerURLFetcherFactory() override {}
251   DistillerURLFetcher* CreateDistillerURLFetcher() const override {
252     return new TestDistillerURLFetcher(false);
253   }
254 };
255
256 class MockDistillerURLFetcherFactory : public DistillerURLFetcherFactory {
257  public:
258   MockDistillerURLFetcherFactory() : DistillerURLFetcherFactory(NULL) {}
259   virtual ~MockDistillerURLFetcherFactory() {}
260
261   MOCK_CONST_METHOD0(CreateDistillerURLFetcher, DistillerURLFetcher*());
262 };
263
264 class DistillerTest : public testing::Test {
265  public:
266   ~DistillerTest() override {}
267
268   void OnDistillArticleDone(scoped_ptr<DistilledArticleProto> proto) {
269     article_proto_ = proto.Pass();
270   }
271
272   void OnDistillArticleUpdate(const ArticleDistillationUpdate& article_update) {
273     in_sequence_updates_.push_back(article_update);
274   }
275
276   void DistillPage(const std::string& url,
277                    scoped_ptr<DistillerPage> distiller_page) {
278     distiller_->DistillPage(GURL(url),
279                             distiller_page.Pass(),
280                             base::Bind(&DistillerTest::OnDistillArticleDone,
281                                        base::Unretained(this)),
282                             base::Bind(&DistillerTest::OnDistillArticleUpdate,
283                                        base::Unretained(this)));
284   }
285
286  protected:
287   scoped_ptr<DistillerImpl> distiller_;
288   scoped_ptr<DistilledArticleProto> article_proto_;
289   std::vector<ArticleDistillationUpdate> in_sequence_updates_;
290   MockDistillerPageFactory page_factory_;
291   TestDistillerURLFetcherFactory url_fetcher_factory_;
292 };
293
294 ACTION_P3(DistillerPageOnDistillationDone, distiller_page, url, result) {
295   distiller_page->OnDistillationDone(url, result);
296 }
297
298 scoped_ptr<DistillerPage> CreateMockDistillerPage(const base::Value* result,
299                                                   const GURL& url) {
300   MockDistillerPage* distiller_page = new MockDistillerPage();
301   EXPECT_CALL(*distiller_page, DistillPageImpl(url, _))
302       .WillOnce(DistillerPageOnDistillationDone(distiller_page, url, result));
303   return scoped_ptr<DistillerPage>(distiller_page).Pass();
304 }
305
306 scoped_ptr<DistillerPage> CreateMockDistillerPageWithPendingJSCallback(
307     MockDistillerPage** distiller_page_ptr,
308     const GURL& url) {
309   MockDistillerPage* distiller_page = new MockDistillerPage();
310   *distiller_page_ptr = distiller_page;
311   EXPECT_CALL(*distiller_page, DistillPageImpl(url, _));
312   return scoped_ptr<DistillerPage>(distiller_page).Pass();
313 }
314
315 scoped_ptr<DistillerPage> CreateMockDistillerPages(
316     MultipageDistillerData* distiller_data,
317     size_t pages_size,
318     int start_page_num) {
319   MockDistillerPage* distiller_page = new MockDistillerPage();
320   {
321     testing::InSequence s;
322     vector<int> page_nums = GetPagesInSequence(start_page_num, pages_size);
323     for (size_t page_num = 0; page_num < pages_size; ++page_num) {
324       int page = page_nums[page_num];
325       GURL url = GURL(distiller_data->page_urls[page]);
326       EXPECT_CALL(*distiller_page, DistillPageImpl(url, _))
327           .WillOnce(DistillerPageOnDistillationDone(
328               distiller_page, url, distiller_data->distilled_values[page]));
329     }
330   }
331   return scoped_ptr<DistillerPage>(distiller_page).Pass();
332 }
333
334 TEST_F(DistillerTest, DistillPage) {
335   base::MessageLoopForUI loop;
336   scoped_ptr<base::Value> result =
337       CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), "");
338   distiller_.reset(
339       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
340   DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
341   base::MessageLoop::current()->RunUntilIdle();
342   EXPECT_EQ(kTitle, article_proto_->title());
343   ASSERT_EQ(article_proto_->pages_size(), 1);
344   const DistilledPageProto& first_page = article_proto_->pages(0);
345   EXPECT_EQ(kContent, first_page.html());
346   EXPECT_EQ(kURL, first_page.url());
347 }
348
349 TEST_F(DistillerTest, DistillPageWithDebugInfo) {
350   base::MessageLoopForUI loop;
351   DomDistillerResult dd_result;
352   dd_result.mutable_debug_info()->set_log(kDebugLog);
353   scoped_ptr<base::Value> result =
354       dom_distiller::proto::json::DomDistillerResult::WriteToValue(dd_result);
355   distiller_.reset(
356       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
357   DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
358   base::MessageLoop::current()->RunUntilIdle();
359   const DistilledPageProto& first_page = article_proto_->pages(0);
360   EXPECT_EQ(kDebugLog, first_page.debug_info().log());
361 }
362
363 TEST_F(DistillerTest, DistillPageWithImages) {
364   base::MessageLoopForUI loop;
365   vector<int> image_indices;
366   image_indices.push_back(0);
367   image_indices.push_back(1);
368   scoped_ptr<base::Value> result =
369       CreateDistilledValueReturnedFromJS(kTitle, kContent, image_indices, "");
370   distiller_.reset(
371       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
372   DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
373   base::MessageLoop::current()->RunUntilIdle();
374   EXPECT_EQ(kTitle, article_proto_->title());
375   ASSERT_EQ(article_proto_->pages_size(), 1);
376   const DistilledPageProto& first_page = article_proto_->pages(0);
377   EXPECT_EQ(kContent, first_page.html());
378   EXPECT_EQ(kURL, first_page.url());
379   ASSERT_EQ(2, first_page.image_size());
380   EXPECT_EQ(kImageData[0], first_page.image(0).data());
381   EXPECT_EQ(GetImageName(1, 0), first_page.image(0).name());
382   EXPECT_EQ(kImageData[1], first_page.image(1).data());
383   EXPECT_EQ(GetImageName(1, 1), first_page.image(1).name());
384 }
385
386 TEST_F(DistillerTest, DistillMultiplePages) {
387   base::MessageLoopForUI loop;
388   const size_t kNumPages = 8;
389   scoped_ptr<MultipageDistillerData> distiller_data =
390       CreateMultipageDistillerDataWithoutImages(kNumPages);
391
392   // Add images.
393   int next_image_number = 0;
394   for (size_t page_num = 0; page_num < kNumPages; ++page_num) {
395     // Each page has different number of images.
396     size_t tot_images = (page_num + kTotalImages) % (kTotalImages + 1);
397     vector<int> image_indices;
398     for (size_t img_num = 0; img_num < tot_images; img_num++) {
399       image_indices.push_back(next_image_number);
400       next_image_number = (next_image_number + 1) % kTotalImages;
401     }
402     distiller_data->image_ids.push_back(image_indices);
403   }
404
405   distiller_.reset(
406       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
407   DistillPage(
408       distiller_data->page_urls[0],
409       CreateMockDistillerPages(distiller_data.get(), kNumPages, 0).Pass());
410   base::MessageLoop::current()->RunUntilIdle();
411   VerifyArticleProtoMatchesMultipageData(
412       article_proto_.get(), distiller_data.get(), kNumPages, kNumPages);
413 }
414
415 TEST_F(DistillerTest, DistillLinkLoop) {
416   base::MessageLoopForUI loop;
417   // Create a loop, the next page is same as the current page. This could
418   // happen if javascript misparses a next page link.
419   scoped_ptr<base::Value> result =
420       CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), kURL);
421   distiller_.reset(
422       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
423   DistillPage(kURL, CreateMockDistillerPage(result.get(), GURL(kURL)).Pass());
424   base::MessageLoop::current()->RunUntilIdle();
425   EXPECT_EQ(kTitle, article_proto_->title());
426   EXPECT_EQ(article_proto_->pages_size(), 1);
427 }
428
429 TEST_F(DistillerTest, CheckMaxPageLimitExtraPage) {
430   base::MessageLoopForUI loop;
431   const size_t kMaxPagesInArticle = 10;
432   scoped_ptr<MultipageDistillerData> distiller_data =
433       CreateMultipageDistillerDataWithoutImages(kMaxPagesInArticle);
434
435   // Note: Next page url of the last page of article is set. So distiller will
436   // try to do kMaxPagesInArticle + 1 calls if the max article limit does not
437   // work.
438   scoped_ptr<base::Value> last_page_data =
439       CreateDistilledValueReturnedFromJS(
440           kTitle,
441           distiller_data->content[kMaxPagesInArticle - 1],
442           vector<int>(),
443           "",
444           distiller_data->page_urls[kMaxPagesInArticle - 2]);
445
446   distiller_data->distilled_values.pop_back();
447   distiller_data->distilled_values.push_back(last_page_data.release());
448
449   distiller_.reset(
450       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
451
452   distiller_->SetMaxNumPagesInArticle(kMaxPagesInArticle);
453
454   DistillPage(distiller_data->page_urls[0],
455               CreateMockDistillerPages(
456                   distiller_data.get(), kMaxPagesInArticle, 0).Pass());
457   base::MessageLoop::current()->RunUntilIdle();
458   EXPECT_EQ(kTitle, article_proto_->title());
459   EXPECT_EQ(kMaxPagesInArticle,
460             static_cast<size_t>(article_proto_->pages_size()));
461 }
462
463 TEST_F(DistillerTest, CheckMaxPageLimitExactLimit) {
464   base::MessageLoopForUI loop;
465   const size_t kMaxPagesInArticle = 10;
466   scoped_ptr<MultipageDistillerData> distiller_data =
467       CreateMultipageDistillerDataWithoutImages(kMaxPagesInArticle);
468
469   distiller_.reset(
470       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
471
472   // Check if distilling an article with exactly the page limit works.
473   distiller_->SetMaxNumPagesInArticle(kMaxPagesInArticle);
474
475   DistillPage(distiller_data->page_urls[0],
476               CreateMockDistillerPages(
477                   distiller_data.get(), kMaxPagesInArticle, 0).Pass());
478   base::MessageLoop::current()->RunUntilIdle();
479   EXPECT_EQ(kTitle, article_proto_->title());
480   EXPECT_EQ(kMaxPagesInArticle,
481             static_cast<size_t>(article_proto_->pages_size()));
482 }
483
484 TEST_F(DistillerTest, SinglePageDistillationFailure) {
485   base::MessageLoopForUI loop;
486   // To simulate failure return a null value.
487   scoped_ptr<base::Value> nullValue(base::Value::CreateNullValue());
488   distiller_.reset(
489       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
490   DistillPage(kURL,
491               CreateMockDistillerPage(nullValue.get(), GURL(kURL)).Pass());
492   base::MessageLoop::current()->RunUntilIdle();
493   EXPECT_EQ("", article_proto_->title());
494   EXPECT_EQ(0, article_proto_->pages_size());
495 }
496
497 TEST_F(DistillerTest, MultiplePagesDistillationFailure) {
498   base::MessageLoopForUI loop;
499   const size_t kNumPages = 8;
500   scoped_ptr<MultipageDistillerData> distiller_data =
501       CreateMultipageDistillerDataWithoutImages(kNumPages);
502
503   // The page number of the failed page.
504   size_t failed_page_num = 3;
505   // reset distilled data of the failed page.
506   distiller_data->distilled_values.erase(
507       distiller_data->distilled_values.begin() + failed_page_num);
508   distiller_data->distilled_values.insert(
509       distiller_data->distilled_values.begin() + failed_page_num,
510       base::Value::CreateNullValue());
511   // Expect only calls till the failed page number.
512   distiller_.reset(
513       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
514   DistillPage(distiller_data->page_urls[0],
515               CreateMockDistillerPages(
516                   distiller_data.get(), failed_page_num + 1, 0).Pass());
517   base::MessageLoop::current()->RunUntilIdle();
518   EXPECT_EQ(kTitle, article_proto_->title());
519   VerifyArticleProtoMatchesMultipageData(
520       article_proto_.get(), distiller_data.get(), failed_page_num, kNumPages);
521 }
522
523 TEST_F(DistillerTest, DistillPreviousPage) {
524   base::MessageLoopForUI loop;
525   const size_t kNumPages = 8;
526
527   // The page number of the article on which distillation starts.
528   int start_page_num = 3;
529   scoped_ptr<MultipageDistillerData> distiller_data =
530       CreateMultipageDistillerDataWithoutImages(kNumPages);
531
532   distiller_.reset(
533       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
534   DistillPage(distiller_data->page_urls[start_page_num],
535               CreateMockDistillerPages(
536                   distiller_data.get(), kNumPages, start_page_num).Pass());
537   base::MessageLoop::current()->RunUntilIdle();
538   VerifyArticleProtoMatchesMultipageData(
539       article_proto_.get(), distiller_data.get(), kNumPages, kNumPages);
540 }
541
542 TEST_F(DistillerTest, IncrementalUpdates) {
543   base::MessageLoopForUI loop;
544   const size_t kNumPages = 8;
545
546   // The page number of the article on which distillation starts.
547   int start_page_num = 3;
548   scoped_ptr<MultipageDistillerData> distiller_data =
549       CreateMultipageDistillerDataWithoutImages(kNumPages);
550
551   distiller_.reset(
552       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
553   DistillPage(distiller_data->page_urls[start_page_num],
554               CreateMockDistillerPages(
555                   distiller_data.get(), kNumPages, start_page_num).Pass());
556   base::MessageLoop::current()->RunUntilIdle();
557   EXPECT_EQ(kTitle, article_proto_->title());
558   ASSERT_EQ(kNumPages, static_cast<size_t>(article_proto_->pages_size()));
559   EXPECT_EQ(kNumPages, in_sequence_updates_.size());
560
561   VerifyIncrementalUpdatesMatch(
562       distiller_data.get(), kNumPages, in_sequence_updates_, start_page_num);
563 }
564
565 TEST_F(DistillerTest, IncrementalUpdatesDoNotDeleteFinalArticle) {
566   base::MessageLoopForUI loop;
567   const size_t kNumPages = 8;
568   int start_page_num = 3;
569   scoped_ptr<MultipageDistillerData> distiller_data =
570       CreateMultipageDistillerDataWithoutImages(kNumPages);
571
572   distiller_.reset(
573       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
574   DistillPage(distiller_data->page_urls[start_page_num],
575               CreateMockDistillerPages(
576                   distiller_data.get(), kNumPages, start_page_num).Pass());
577   base::MessageLoop::current()->RunUntilIdle();
578   EXPECT_EQ(kNumPages, in_sequence_updates_.size());
579
580   in_sequence_updates_.clear();
581
582   // Should still be able to access article and pages.
583   VerifyArticleProtoMatchesMultipageData(
584       article_proto_.get(), distiller_data.get(), kNumPages, kNumPages);
585 }
586
587 TEST_F(DistillerTest, DeletingArticleDoesNotInterfereWithUpdates) {
588   base::MessageLoopForUI loop;
589   const size_t kNumPages = 8;
590   scoped_ptr<MultipageDistillerData> distiller_data =
591       CreateMultipageDistillerDataWithoutImages(kNumPages);
592   // The page number of the article on which distillation starts.
593   int start_page_num = 3;
594
595   distiller_.reset(
596       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
597   DistillPage(distiller_data->page_urls[start_page_num],
598               CreateMockDistillerPages(
599                   distiller_data.get(), kNumPages, start_page_num).Pass());
600   base::MessageLoop::current()->RunUntilIdle();
601   EXPECT_EQ(kNumPages, in_sequence_updates_.size());
602   EXPECT_EQ(kTitle, article_proto_->title());
603   ASSERT_EQ(kNumPages, static_cast<size_t>(article_proto_->pages_size()));
604
605   // Delete the article.
606   article_proto_.reset();
607   VerifyIncrementalUpdatesMatch(
608       distiller_data.get(), kNumPages, in_sequence_updates_, start_page_num);
609 }
610
611 TEST_F(DistillerTest, CancelWithDelayedImageFetchCallback) {
612   base::MessageLoopForUI loop;
613   vector<int> image_indices;
614   image_indices.push_back(0);
615   scoped_ptr<base::Value> distilled_value =
616       CreateDistilledValueReturnedFromJS(kTitle, kContent, image_indices, "");
617   TestDistillerURLFetcher* delayed_fetcher = new TestDistillerURLFetcher(true);
618   MockDistillerURLFetcherFactory mock_url_fetcher_factory;
619   EXPECT_CALL(mock_url_fetcher_factory, CreateDistillerURLFetcher())
620       .WillOnce(Return(delayed_fetcher));
621   distiller_.reset(
622       new DistillerImpl(mock_url_fetcher_factory, DomDistillerOptions()));
623   DistillPage(
624       kURL, CreateMockDistillerPage(distilled_value.get(), GURL(kURL)).Pass());
625   base::MessageLoop::current()->RunUntilIdle();
626
627   // Post callback from the url fetcher and then delete the distiller.
628   delayed_fetcher->PostCallbackTask();
629   distiller_.reset();
630
631   base::MessageLoop::current()->RunUntilIdle();
632 }
633
634 TEST_F(DistillerTest, CancelWithDelayedJSCallback) {
635   base::MessageLoopForUI loop;
636   scoped_ptr<base::Value> distilled_value =
637       CreateDistilledValueReturnedFromJS(kTitle, kContent, vector<int>(), "");
638   MockDistillerPage* distiller_page = NULL;
639   distiller_.reset(
640       new DistillerImpl(url_fetcher_factory_, DomDistillerOptions()));
641   DistillPage(kURL,
642               CreateMockDistillerPageWithPendingJSCallback(&distiller_page,
643                                                            GURL(kURL)));
644   base::MessageLoop::current()->RunUntilIdle();
645
646   ASSERT_TRUE(distiller_page);
647   // Post the task to execute javascript and then delete the distiller.
648   distiller_page->OnDistillationDone(GURL(kURL), distilled_value.get());
649   distiller_.reset();
650
651   base::MessageLoop::current()->RunUntilIdle();
652 }
653
654 }  // namespace dom_distiller