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