chromium/ios/chrome/browser/reading_list/model/reading_list_web_state_observer_unittest.mm

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/chrome/browser/reading_list/model/reading_list_web_state_observer.h"

#import <memory>

#import "base/memory/scoped_refptr.h"
#import "base/time/default_clock.h"
#import "components/reading_list/core/reading_list_model.h"
#import "ios/chrome/browser/reading_list/model/offline_url_utils.h"
#import "ios/chrome/browser/reading_list/model/reading_list_model_factory.h"
#import "ios/chrome/browser/reading_list/model/reading_list_test_utils.h"
#import "ios/chrome/browser/shared/model/profile/test/test_profile_ios.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/reload_type.h"
#import "ios/web/public/test/fakes/fake_navigation_manager.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "net/base/network_change_notifier.h"
#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"

namespace {
const char kTestURL[] = "http://foo.bar";
const char kTestTitle[] = "title";
const char kTestDistilledPath[] = "distilled/page.html";
const char kTestDistilledURL[] = "http://foo.bar/distilled";

// A Test navigation manager that checks if Reload was called.
class FakeNavigationManager : public web::FakeNavigationManager {
 public:
  void Reload(web::ReloadType reload_type, bool check_for_repost) override {
    reload_called_ = true;
  }

  bool ReloadCalled() { return reload_called_; }

  int GetLastCommittedItemIndex() const override { return 1; }

  void GoToIndex(int index) override { go_to_index_called_index_ = index; }

  int GoToIndexCalled() { return go_to_index_called_index_; }

 private:
  bool reload_called_ = false;
  int go_to_index_called_index_ = -1;
};

// WebState that remembers the last opened parameters.
class FakeWebState : public web::FakeWebState {
 public:
  void OpenURL(const web::WebState::OpenURLParams& params) override {
    last_opened_url_ = params.url;
  }
  const GURL& LastOpenedUrl() { return last_opened_url_; }

 private:
  GURL last_opened_url_;
};

}  // namespace

// Test fixture to test loading of Reading list entries.
class ReadingListWebStateObserverTest : public PlatformTest {
 public:
  void SetUp() override {
    PlatformTest::SetUp();

    std::vector<scoped_refptr<ReadingListEntry>> initial_entries;
    initial_entries.push_back(base::MakeRefCounted<ReadingListEntry>(
        GURL(kTestURL), kTestTitle, base::Time::Now()));

    TestChromeBrowserState::Builder builder;
    builder.AddTestingFactory(
        ReadingListModelFactory::GetInstance(),
        base::BindRepeating(&BuildReadingListModelWithFakeStorage,
                            std::move(initial_entries)));
    browser_state_ = std::move(builder).Build();

    test_web_state_.SetBrowserState(browser_state_.get());

    auto fake_navigation_manager = std::make_unique<FakeNavigationManager>();
    GURL url(kTestURL);
    std::unique_ptr<web::NavigationItem> pending_item =
        web::NavigationItem::Create();
    pending_item->SetURL(url);
    std::unique_ptr<web::NavigationItem> last_committed_item =
        web::NavigationItem::Create();

    fake_navigation_manager->SetPendingItem(pending_item.release());
    fake_navigation_manager->SetLastCommittedItem(
        last_committed_item.release());
    test_web_state_.SetNavigationManager(std::move(fake_navigation_manager));

    ReadingListWebStateObserver::CreateForWebState(&test_web_state_,
                                                   reading_list_model());
  }

  ReadingListModel* reading_list_model() {
    return ReadingListModelFactory::GetForBrowserState(browser_state_.get());
  }

 protected:
  web::WebTaskEnvironment task_environment_;
  std::unique_ptr<TestChromeBrowserState> browser_state_;
  FakeWebState test_web_state_;
};

// Tests that failing loading an online version does not mark it read.
TEST_F(ReadingListWebStateObserverTest, TestLoadReadingListFailure) {
  GURL url(kTestURL);
  scoped_refptr<const ReadingListEntry> entry =
      reading_list_model()->GetEntryByURL(url);
  FakeNavigationManager* fake_navigation_manager =
      static_cast<FakeNavigationManager*>(
          test_web_state_.GetNavigationManager());

  test_web_state_.SetLoading(true);
  test_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::FAILURE);
  test_web_state_.SetLoading(false);

  EXPECT_FALSE(fake_navigation_manager->ReloadCalled());
  EXPECT_EQ(fake_navigation_manager->GoToIndexCalled(), -1);
  // Check that `GetLastCommittedItem()` has not been altered.
  EXPECT_EQ(fake_navigation_manager->GetLastCommittedItem()->GetVirtualURL(),
            GURL());
  EXPECT_EQ(fake_navigation_manager->GetLastCommittedItem()->GetURL(), GURL());
  EXPECT_FALSE(entry->IsRead());
}

// Tests that loading an online version of an entry does not alter navigation
// stack and mark entry read.
TEST_F(ReadingListWebStateObserverTest, TestLoadReadingListOnline) {
  GURL url(kTestURL);
  std::string distilled_path = kTestDistilledPath;
  reading_list_model()->SetEntryDistilledInfoIfExists(
      url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
      base::Time::FromTimeT(100));
  scoped_refptr<const ReadingListEntry> entry =
      reading_list_model()->GetEntryByURL(url);

  FakeNavigationManager* fake_navigation_manager =
      static_cast<FakeNavigationManager*>(
          test_web_state_.GetNavigationManager());
  fake_navigation_manager->GetPendingItem()->SetURL(url);

  test_web_state_.SetLoading(true);
  test_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS);
  test_web_state_.SetLoading(false);

  EXPECT_FALSE(fake_navigation_manager->ReloadCalled());
  EXPECT_EQ(fake_navigation_manager->GoToIndexCalled(), -1);
  // Check that `GetLastCommittedItem()` has not been altered.
  EXPECT_EQ(fake_navigation_manager->GetLastCommittedItem()->GetVirtualURL(),
            GURL());
  EXPECT_EQ(fake_navigation_manager->GetLastCommittedItem()->GetURL(), GURL());
  EXPECT_TRUE(entry->IsRead());
}

// Tests that loading an online version of an entry does update navigation
// stack and mark entry read.
TEST_F(ReadingListWebStateObserverTest, TestLoadReadingListDistilledCommitted) {
  GURL url(kTestURL);
  std::string distilled_path = kTestDistilledPath;
  reading_list_model()->SetEntryDistilledInfoIfExists(
      url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
      base::Time::FromTimeT(100));
  scoped_refptr<const ReadingListEntry> entry =
      reading_list_model()->GetEntryByURL(url);
  GURL distilled_url = reading_list::OfflineURLForURL(entry->URL());

  FakeNavigationManager* fake_navigation_manager =
      static_cast<FakeNavigationManager*>(
          test_web_state_.GetNavigationManager());

  // Test on committed entry, there must be no pending item.
  fake_navigation_manager->SetPendingItem(nullptr);
  fake_navigation_manager->GetLastCommittedItem()->SetURL(url);

  test_web_state_.SetLoading(true);
  test_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::FAILURE);
  test_web_state_.SetLoading(false);

  EXPECT_FALSE(fake_navigation_manager->ReloadCalled());
  EXPECT_EQ(fake_navigation_manager->GoToIndexCalled(),
            fake_navigation_manager->GetLastCommittedItemIndex());
  EXPECT_EQ(fake_navigation_manager->GetLastCommittedItem()->GetVirtualURL(),
            url);
  EXPECT_EQ(fake_navigation_manager->GetLastCommittedItem()->GetURL(),
            distilled_url);
  EXPECT_TRUE(entry->IsRead());
}

// Tests that loading an online version of a pending entry on reload does update
// committed entry, reload, and mark entry read.
TEST_F(ReadingListWebStateObserverTest, TestLoadReadingListDistilledPending) {
  GURL url(kTestURL);
  std::string distilled_path = kTestDistilledPath;
  reading_list_model()->SetEntryDistilledInfoIfExists(
      url, base::FilePath(distilled_path), GURL(kTestDistilledURL), 50,
      base::Time::FromTimeT(100));
  scoped_refptr<const ReadingListEntry> entry =
      reading_list_model()->GetEntryByURL(url);
  GURL distilled_url = reading_list::OfflineURLForURL(entry->URL());

  FakeNavigationManager* fake_navigation_manager =
      static_cast<FakeNavigationManager*>(
          test_web_state_.GetNavigationManager());

  fake_navigation_manager->SetPendingItem(nil);
  fake_navigation_manager->GetLastCommittedItem()->SetURL(url);

  test_web_state_.SetLoading(true);
  test_web_state_.OnPageLoaded(web::PageLoadCompletionStatus::FAILURE);
  test_web_state_.SetLoading(false);

  EXPECT_FALSE(fake_navigation_manager->ReloadCalled());
  EXPECT_EQ(fake_navigation_manager->GoToIndexCalled(),
            fake_navigation_manager->GetLastCommittedItemIndex());
  EXPECT_EQ(fake_navigation_manager->GetLastCommittedItem()->GetVirtualURL(),
            url);
  EXPECT_EQ(fake_navigation_manager->GetLastCommittedItem()->GetURL(),
            distilled_url);

  EXPECT_TRUE(entry->IsRead());
}