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

#include "chrome/browser/extensions/chrome_app_sorting.h"

#include <memory>

#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/extensions/extension_prefs_unittest.h"
#include "components/crx_file/id_util.h"
#include "components/sync/model/string_ordinal.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/manifest_constants.h"
#include "testing/gtest/include/gtest/gtest.h"

using extensions::mojom::ManifestLocation;

namespace extensions {

namespace keys = manifest_keys;

class ChromeAppSortingAppLocation : public ExtensionPrefsTest {
 public:
  void Initialize() override {
    extension_ = prefs_.AddExtension("not_an_app");
    // Non-apps should not have any app launch ordinal or page ordinal.
    prefs()->OnExtensionInstalled(extension_.get(),
                                  /*disable_reasons=*/{},
                                  syncer::StringOrdinal(), std::string());
  }

  void Verify() override {
    EXPECT_FALSE(
        app_sorting()->GetAppLaunchOrdinal(extension_->id()).IsValid());
    EXPECT_FALSE(app_sorting()->GetPageOrdinal(extension_->id()).IsValid());
  }

 private:
  scoped_refptr<Extension> extension_;
};
TEST_F(ChromeAppSortingAppLocation, ChromeAppSortingAppLocation) {}

class ChromeAppSortingAppLaunchOrdinal : public ExtensionPrefsTest {
 public:
  void Initialize() override {
    // No extensions yet.
    syncer::StringOrdinal page = syncer::StringOrdinal::CreateInitialOrdinal();
    EXPECT_TRUE(syncer::StringOrdinal::CreateInitialOrdinal().Equals(
        app_sorting()->CreateNextAppLaunchOrdinal(page)));

    extension_ = prefs_.AddApp("on_extension_installed");
    EXPECT_FALSE(prefs()->IsExtensionDisabled(extension_->id()));
    prefs()->OnExtensionInstalled(extension_.get(),
                                  /*disable_reasons=*/{},
                                  syncer::StringOrdinal(), std::string());
  }

  void Verify() override {
    syncer::StringOrdinal launch_ordinal =
        app_sorting()->GetAppLaunchOrdinal(extension_->id());
    syncer::StringOrdinal page_ordinal =
        syncer::StringOrdinal::CreateInitialOrdinal();

    // Extension should have been assigned a valid StringOrdinal.
    EXPECT_TRUE(launch_ordinal.IsValid());
    EXPECT_TRUE(launch_ordinal.LessThan(
        app_sorting()->CreateNextAppLaunchOrdinal(page_ordinal)));
    // Set a new launch ordinal of and verify it comes after.
    app_sorting()->SetAppLaunchOrdinal(
        extension_->id(),
        app_sorting()->CreateNextAppLaunchOrdinal(page_ordinal));
    syncer::StringOrdinal new_launch_ordinal =
        app_sorting()->GetAppLaunchOrdinal(extension_->id());
    EXPECT_TRUE(launch_ordinal.LessThan(new_launch_ordinal));

    // This extension doesn't exist, so it should return an invalid
    // StringOrdinal.
    syncer::StringOrdinal invalid_app_launch_ordinal =
        app_sorting()->GetAppLaunchOrdinal("foo");
    EXPECT_FALSE(invalid_app_launch_ordinal.IsValid());
    EXPECT_EQ(-1, app_sorting()->PageStringOrdinalAsInteger(
        invalid_app_launch_ordinal));

    // The second page doesn't have any apps so its next launch ordinal should
    // be the first launch ordinal.
    syncer::StringOrdinal next_page = page_ordinal.CreateAfter();
    syncer::StringOrdinal next_page_app_launch_ordinal =
        app_sorting()->CreateNextAppLaunchOrdinal(next_page);
    EXPECT_TRUE(next_page_app_launch_ordinal.Equals(
        app_sorting()->CreateFirstAppLaunchOrdinal(next_page)));
  }

 private:
  scoped_refptr<Extension> extension_;
};
TEST_F(ChromeAppSortingAppLaunchOrdinal, ChromeAppSortingAppLaunchOrdinal) {}

class ChromeAppSortingPageOrdinal : public ExtensionPrefsTest {
 public:
  void Initialize() override {
    extension_ = prefs_.AddApp("page_ordinal");
    // Install with a page preference.
    first_page_ = syncer::StringOrdinal::CreateInitialOrdinal();
    prefs()->OnExtensionInstalled(extension_.get(),
                                  /*disable_reasons=*/{}, first_page_,
                                  std::string());
    EXPECT_TRUE(first_page_.Equals(
        app_sorting()->GetPageOrdinal(extension_->id())));
    EXPECT_EQ(0, app_sorting()->PageStringOrdinalAsInteger(first_page_));

    scoped_refptr<Extension> extension2 = prefs_.AddApp("page_ordinal_2");
    // Install without any page preference.
    prefs()->OnExtensionInstalled(extension2.get(),
                                  /*disable_reasons=*/{},
                                  syncer::StringOrdinal(), std::string());
    EXPECT_TRUE(first_page_.Equals(
        app_sorting()->GetPageOrdinal(extension2->id())));
  }
  void Verify() override {
    // Set the page ordinal.
    syncer::StringOrdinal new_page = first_page_.CreateAfter();
    app_sorting()->SetPageOrdinal(extension_->id(), new_page);
    // Verify the page ordinal.
    EXPECT_TRUE(
        new_page.Equals(app_sorting()->GetPageOrdinal(extension_->id())));
    EXPECT_EQ(1, app_sorting()->PageStringOrdinalAsInteger(new_page));

    // This extension doesn't exist, so it should return an invalid
    // StringOrdinal.
    EXPECT_FALSE(app_sorting()->GetPageOrdinal("foo").IsValid());
  }

 private:
  syncer::StringOrdinal first_page_;
  scoped_refptr<Extension> extension_;
};
TEST_F(ChromeAppSortingPageOrdinal, ChromeAppSortingPageOrdinal) {}

// Ensure that ChromeAppSorting is able to properly initialize off a set
// of old page and app launch indices and properly convert them.
class ChromeAppSortingInitialize : public PrefsPrepopulatedTestBase {
 public:
  ChromeAppSortingInitialize() = default;
  ~ChromeAppSortingInitialize() override = default;

  void Initialize() override {
    // A preference determining the order of which the apps appear on the NTP.
    const char kPrefAppLaunchIndexDeprecated[] = "app_launcher_index";
    // A preference determining the page on which an app appears in the NTP.
    const char kPrefPageIndexDeprecated[] = "page_index";

    // Setup the deprecated preferences.
    prefs()->UpdateExtensionPref(extension1()->id(),
                                 kPrefAppLaunchIndexDeprecated, base::Value(0));
    prefs()->UpdateExtensionPref(extension1()->id(), kPrefPageIndexDeprecated,
                                 base::Value(0));

    prefs()->UpdateExtensionPref(extension2()->id(),
                                 kPrefAppLaunchIndexDeprecated, base::Value(1));
    prefs()->UpdateExtensionPref(extension2()->id(), kPrefPageIndexDeprecated,
                                 base::Value(0));

    prefs()->UpdateExtensionPref(extension3()->id(),
                                 kPrefAppLaunchIndexDeprecated, base::Value(0));
    prefs()->UpdateExtensionPref(extension3()->id(), kPrefPageIndexDeprecated,
                                 base::Value(1));

    // We insert the ids in reverse order so that we have to deal with the
    // element on the 2nd page before the 1st page is seen.
    ExtensionIdList ids;
    ids.push_back(extension3()->id());
    ids.push_back(extension2()->id());
    ids.push_back(extension1()->id());

    app_sorting()->MigrateAppIndex(ids);
  }
  void Verify() override {
    syncer::StringOrdinal first_ordinal =
        syncer::StringOrdinal::CreateInitialOrdinal();

    EXPECT_TRUE(first_ordinal.Equals(
        app_sorting()->GetAppLaunchOrdinal(extension1()->id())));
    EXPECT_TRUE(first_ordinal.LessThan(
        app_sorting()->GetAppLaunchOrdinal(extension2()->id())));
    EXPECT_TRUE(first_ordinal.Equals(
        app_sorting()->GetAppLaunchOrdinal(extension3()->id())));

    EXPECT_TRUE(first_ordinal.Equals(
        app_sorting()->GetPageOrdinal(extension1()->id())));
    EXPECT_TRUE(first_ordinal.Equals(
        app_sorting()->GetPageOrdinal(extension2()->id())));
    EXPECT_TRUE(first_ordinal.LessThan(
        app_sorting()->GetPageOrdinal(extension3()->id())));
  }
};
TEST_F(ChromeAppSortingInitialize, ChromeAppSortingInitialize) {}

// Make sure that initialization still works when no extensions are present
// (i.e. make sure that the web store icon is still loaded into the map).
class ChromeAppSortingInitializeWithNoApps : public PrefsPrepopulatedTestBase {
 public:
  ChromeAppSortingInitializeWithNoApps() = default;
  ~ChromeAppSortingInitializeWithNoApps() override = default;

  void Initialize() override {
    // Make sure that the web store has valid ordinals.
    syncer::StringOrdinal initial_ordinal =
        syncer::StringOrdinal::CreateInitialOrdinal();
    app_sorting()->SetPageOrdinal(kWebStoreAppId, initial_ordinal);
    app_sorting()->SetAppLaunchOrdinal(kWebStoreAppId, initial_ordinal);
  }
  void Verify() override {
    syncer::StringOrdinal page = app_sorting()->GetPageOrdinal(kWebStoreAppId);
    EXPECT_TRUE(page.IsValid());

    auto page_it = app_sorting()->ntp_ordinal_map_.find(page);
    EXPECT_TRUE(page_it != app_sorting()->ntp_ordinal_map_.end());

    syncer::StringOrdinal app_launch =
        app_sorting()->GetPageOrdinal(kWebStoreAppId);
    EXPECT_TRUE(app_launch.IsValid());

    auto app_launch_it = page_it->second.find(app_launch);
    EXPECT_TRUE(app_launch_it != page_it->second.end());
  }
};
TEST_F(ChromeAppSortingInitializeWithNoApps,
       ChromeAppSortingInitializeWithNoApps) {}

// Tests the application index to ordinal migration code for values that
// shouldn't be converted. This should be removed when the migrate code
// is taken out.
// http://crbug.com/40052100
class ChromeAppSortingMigrateAppIndexInvalid
    : public PrefsPrepopulatedTestBase {
 public:
  ChromeAppSortingMigrateAppIndexInvalid() = default;
  ~ChromeAppSortingMigrateAppIndexInvalid() override = default;

  void Initialize() override {
    // A preference determining the order of which the apps appear on the NTP.
    const char kPrefAppLaunchIndexDeprecated[] = "app_launcher_index";
    // A preference determining the page on which an app appears in the NTP.
    const char kPrefPageIndexDeprecated[] = "page_index";

    // Setup the deprecated preference.
    prefs()->UpdateExtensionPref(extension1()->id(),
                                 kPrefAppLaunchIndexDeprecated, base::Value(0));
    prefs()->UpdateExtensionPref(extension1()->id(), kPrefPageIndexDeprecated,
                                 base::Value(-1));
  }
  void Verify() override {
    // Make sure that the invalid page_index wasn't converted over.
    EXPECT_FALSE(app_sorting()->GetAppLaunchOrdinal(
        extension1()->id()).IsValid());
  }
};
TEST_F(ChromeAppSortingMigrateAppIndexInvalid,
       ChromeAppSortingMigrateAppIndexInvalid) {}

class ChromeAppSortingFixNTPCollisionsAllCollide
    : public PrefsPrepopulatedTestBase {
 public:
  ChromeAppSortingFixNTPCollisionsAllCollide() = default;
  ~ChromeAppSortingFixNTPCollisionsAllCollide() override = default;

  void Initialize() override {
    repeated_ordinal_ = syncer::StringOrdinal::CreateInitialOrdinal();

    app_sorting()->SetAppLaunchOrdinal(extension1()->id(),
                                       repeated_ordinal_);
    app_sorting()->SetPageOrdinal(extension1()->id(), repeated_ordinal_);

    app_sorting()->SetAppLaunchOrdinal(extension2()->id(), repeated_ordinal_);
    app_sorting()->SetPageOrdinal(extension2()->id(), repeated_ordinal_);

    app_sorting()->SetAppLaunchOrdinal(extension3()->id(), repeated_ordinal_);
    app_sorting()->SetPageOrdinal(extension3()->id(), repeated_ordinal_);

    app_sorting()->FixNTPOrdinalCollisions();
  }
  void Verify() override {
    syncer::StringOrdinal extension1_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension1()->id());
    syncer::StringOrdinal extension2_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension2()->id());
    syncer::StringOrdinal extension3_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension3()->id());

    // The overlapping extensions should have be adjusted so that they are
    // sorted by their id.
    EXPECT_EQ(extension1()->id() < extension2()->id(),
              extension1_app_launch.LessThan(extension2_app_launch));
    EXPECT_EQ(extension1()->id() < extension3()->id(),
              extension1_app_launch.LessThan(extension3_app_launch));
    EXPECT_EQ(extension2()->id() < extension3()->id(),
              extension2_app_launch.LessThan(extension3_app_launch));

    // The page ordinal should be unchanged.
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension1()->id()).Equals(
        repeated_ordinal_));
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension2()->id()).Equals(
        repeated_ordinal_));
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension3()->id()).Equals(
        repeated_ordinal_));
  }

 private:
  syncer::StringOrdinal repeated_ordinal_;
};
TEST_F(ChromeAppSortingFixNTPCollisionsAllCollide,
       ChromeAppSortingFixNTPCollisionsAllCollide) {}

class ChromeAppSortingFixNTPCollisionsSomeCollideAtStart
    : public PrefsPrepopulatedTestBase {
 public:
  ChromeAppSortingFixNTPCollisionsSomeCollideAtStart() = default;
  ~ChromeAppSortingFixNTPCollisionsSomeCollideAtStart() override = default;

  void Initialize() override {
    first_ordinal_ = syncer::StringOrdinal::CreateInitialOrdinal();
    syncer::StringOrdinal second_ordinal = first_ordinal_.CreateAfter();

    // Have the first two extension in the same position, with a third
    // (non-colliding) extension after.

    app_sorting()->SetAppLaunchOrdinal(extension1()->id(), first_ordinal_);
    app_sorting()->SetPageOrdinal(extension1()->id(), first_ordinal_);

    app_sorting()->SetAppLaunchOrdinal(extension2()->id(), first_ordinal_);
    app_sorting()->SetPageOrdinal(extension2()->id(), first_ordinal_);

    app_sorting()->SetAppLaunchOrdinal(extension3()->id(), second_ordinal);
    app_sorting()->SetPageOrdinal(extension3()->id(), first_ordinal_);

    app_sorting()->FixNTPOrdinalCollisions();
  }
  void Verify() override {
    syncer::StringOrdinal extension1_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension1()->id());
    syncer::StringOrdinal extension2_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension2()->id());
    syncer::StringOrdinal extension3_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension3()->id());

    // The overlapping extensions should have be adjusted so that they are
    // sorted by their id, but they both should be before ext3, which wasn't
    // overlapping.
    EXPECT_EQ(extension1()->id() < extension2()->id(),
              extension1_app_launch.LessThan(extension2_app_launch));
    EXPECT_TRUE(extension1_app_launch.LessThan(extension3_app_launch));
    EXPECT_TRUE(extension2_app_launch.LessThan(extension3_app_launch));

    // The page ordinal should be unchanged.
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension1()->id()).Equals(
        first_ordinal_));
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension2()->id()).Equals(
        first_ordinal_));
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension3()->id()).Equals(
        first_ordinal_));
  }

 private:
  syncer::StringOrdinal first_ordinal_;
};
TEST_F(ChromeAppSortingFixNTPCollisionsSomeCollideAtStart,
       ChromeAppSortingFixNTPCollisionsSomeCollideAtStart) {}

class ChromeAppSortingFixNTPCollisionsSomeCollideAtEnd
    : public PrefsPrepopulatedTestBase {
 public:
  ChromeAppSortingFixNTPCollisionsSomeCollideAtEnd() = default;
  ~ChromeAppSortingFixNTPCollisionsSomeCollideAtEnd() override = default;

  void Initialize() override {
    first_ordinal_ = syncer::StringOrdinal::CreateInitialOrdinal();
    syncer::StringOrdinal second_ordinal = first_ordinal_.CreateAfter();

    // Have the first extension in a non-colliding position, followed by two
    // two extension in the same position.

    app_sorting()->SetAppLaunchOrdinal(extension1()->id(), first_ordinal_);
    app_sorting()->SetPageOrdinal(extension1()->id(), first_ordinal_);

    app_sorting()->SetAppLaunchOrdinal(extension2()->id(), second_ordinal);
    app_sorting()->SetPageOrdinal(extension2()->id(), first_ordinal_);

    app_sorting()->SetAppLaunchOrdinal(extension3()->id(), second_ordinal);
    app_sorting()->SetPageOrdinal(extension3()->id(), first_ordinal_);

    app_sorting()->FixNTPOrdinalCollisions();
  }
  void Verify() override {
    syncer::StringOrdinal extension1_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension1()->id());
    syncer::StringOrdinal extension2_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension2()->id());
    syncer::StringOrdinal extension3_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension3()->id());

    // The overlapping extensions should have be adjusted so that they are
    // sorted by their id, but they both should be after ext1, which wasn't
    // overlapping.
    EXPECT_TRUE(extension1_app_launch.LessThan(extension2_app_launch));
    EXPECT_TRUE(extension1_app_launch.LessThan(extension3_app_launch));
    EXPECT_EQ(extension2()->id() < extension3()->id(),
              extension2_app_launch.LessThan(extension3_app_launch));

    // The page ordinal should be unchanged.
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension1()->id()).Equals(
        first_ordinal_));
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension2()->id()).Equals(
        first_ordinal_));
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension3()->id()).Equals(
        first_ordinal_));
  }

 private:
  syncer::StringOrdinal first_ordinal_;
};
TEST_F(ChromeAppSortingFixNTPCollisionsSomeCollideAtEnd,
       ChromeAppSortingFixNTPCollisionsSomeCollideAtEnd) {}

class ChromeAppSortingFixNTPCollisionsTwoCollisions
    : public PrefsPrepopulatedTestBase {
 public:
  ChromeAppSortingFixNTPCollisionsTwoCollisions() = default;
  ~ChromeAppSortingFixNTPCollisionsTwoCollisions() override = default;

  void Initialize() override {
    first_ordinal_ = syncer::StringOrdinal::CreateInitialOrdinal();
    syncer::StringOrdinal second_ordinal = first_ordinal_.CreateAfter();

    // Have two extensions colliding, followed by two more colliding extensions.
    app_sorting()->SetAppLaunchOrdinal(extension1()->id(), first_ordinal_);
    app_sorting()->SetPageOrdinal(extension1()->id(), first_ordinal_);

    app_sorting()->SetAppLaunchOrdinal(extension2()->id(), first_ordinal_);
    app_sorting()->SetPageOrdinal(extension2()->id(), first_ordinal_);

    app_sorting()->SetAppLaunchOrdinal(extension3()->id(), second_ordinal);
    app_sorting()->SetPageOrdinal(extension3()->id(), first_ordinal_);

    app_sorting()->SetAppLaunchOrdinal(extension4()->id(), second_ordinal);
    app_sorting()->SetPageOrdinal(extension4()->id(), first_ordinal_);

    app_sorting()->FixNTPOrdinalCollisions();
  }
  void Verify() override {
    syncer::StringOrdinal extension1_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension1()->id());
    syncer::StringOrdinal extension2_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension2()->id());
    syncer::StringOrdinal extension3_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension3()->id());
    syncer::StringOrdinal extension4_app_launch =
        app_sorting()->GetAppLaunchOrdinal(extension4()->id());

    // The overlapping extensions should have be adjusted so that they are
    // sorted by their id, with |ext1| and |ext2| appearing before |ext3| and
    // |ext4|.
    EXPECT_TRUE(extension1_app_launch.LessThan(extension3_app_launch));
    EXPECT_TRUE(extension1_app_launch.LessThan(extension4_app_launch));
    EXPECT_TRUE(extension2_app_launch.LessThan(extension3_app_launch));
    EXPECT_TRUE(extension2_app_launch.LessThan(extension4_app_launch));

    EXPECT_EQ(extension1()->id() < extension2()->id(),
              extension1_app_launch.LessThan(extension2_app_launch));
    EXPECT_EQ(extension3()->id() < extension4()->id(),
              extension3_app_launch.LessThan(extension4_app_launch));

    // The page ordinal should be unchanged.
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension1()->id()).Equals(
        first_ordinal_));
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension2()->id()).Equals(
        first_ordinal_));
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension3()->id()).Equals(
        first_ordinal_));
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension4()->id()).Equals(
        first_ordinal_));
  }

 private:
  syncer::StringOrdinal first_ordinal_;
};
TEST_F(ChromeAppSortingFixNTPCollisionsTwoCollisions,
       ChromeAppSortingFixNTPCollisionsTwoCollisions) {}

class ChromeAppSortingEnsureValidOrdinals
    : public PrefsPrepopulatedTestBase {
 public :
  ChromeAppSortingEnsureValidOrdinals() = default;
  ~ChromeAppSortingEnsureValidOrdinals() override = default;

  void Initialize() override {}
  void Verify() override {
    // Give ext1 invalid ordinals and then check that EnsureValidOrdinals fixes
    // them.
    app_sorting()->SetAppLaunchOrdinal(extension1()->id(),
                                     syncer::StringOrdinal());
    app_sorting()->SetPageOrdinal(extension1()->id(), syncer::StringOrdinal());

    app_sorting()->EnsureValidOrdinals(extension1()->id(),
                                     syncer::StringOrdinal());

    EXPECT_TRUE(
        app_sorting()->GetAppLaunchOrdinal(extension1()->id()).IsValid());
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(extension1()->id()).IsValid());
  }
};
TEST_F(ChromeAppSortingEnsureValidOrdinals,
       ChromeAppSortingEnsureValidOrdinals) {}

class ChromeAppSortingPageOrdinalMapping : public PrefsPrepopulatedTestBase {
 public:
  ChromeAppSortingPageOrdinalMapping() = default;
  ~ChromeAppSortingPageOrdinalMapping() override = default;

  void Initialize() override {}
  void Verify() override {
    std::string ext_1 = "ext_1";
    std::string ext_2 = "ext_2";

    syncer::StringOrdinal first_ordinal =
        syncer::StringOrdinal::CreateInitialOrdinal();

    // Ensure attempting to removing a mapping with an invalid page doesn't
    // modify the map.
    EXPECT_TRUE(app_sorting()->ntp_ordinal_map_.empty());
    app_sorting()->RemoveOrdinalMapping(
        ext_1, first_ordinal, first_ordinal);
    EXPECT_TRUE(app_sorting()->ntp_ordinal_map_.empty());

    // Add new mappings.
    app_sorting()->AddOrdinalMapping(ext_1, first_ordinal, first_ordinal);
    app_sorting()->AddOrdinalMapping(ext_2, first_ordinal, first_ordinal);

    EXPECT_EQ(1U, app_sorting()->ntp_ordinal_map_.size());
    EXPECT_EQ(2U, app_sorting()->ntp_ordinal_map_[first_ordinal].size());

    auto it =
        app_sorting()->ntp_ordinal_map_[first_ordinal].find(first_ordinal);
    EXPECT_EQ(ext_1, it->second);
    ++it;
    EXPECT_EQ(ext_2, it->second);

    app_sorting()->RemoveOrdinalMapping(ext_1, first_ordinal, first_ordinal);
    EXPECT_EQ(1U, app_sorting()->ntp_ordinal_map_.size());
    EXPECT_EQ(1U, app_sorting()->ntp_ordinal_map_[first_ordinal].size());

    it = app_sorting()->ntp_ordinal_map_[first_ordinal].find(first_ordinal);
    EXPECT_EQ(ext_2, it->second);

    // Ensure that attempting to remove an extension with a valid page and app
    // launch ordinals, but a unused id has no effect.
    app_sorting()->RemoveOrdinalMapping(
        "invalid_ext", first_ordinal, first_ordinal);
    EXPECT_EQ(1U, app_sorting()->ntp_ordinal_map_.size());
    EXPECT_EQ(1U, app_sorting()->ntp_ordinal_map_[first_ordinal].size());

    it = app_sorting()->ntp_ordinal_map_[first_ordinal].find(first_ordinal);
    EXPECT_EQ(ext_2, it->second);
  }
};
TEST_F(ChromeAppSortingPageOrdinalMapping,
       ChromeAppSortingPageOrdinalMapping) {}

class ChromeAppSortingPreinstalledAppsBase : public PrefsPrepopulatedTestBase {
 public:
  ChromeAppSortingPreinstalledAppsBase() {
    base::DictValue simple_dict;
    simple_dict.Set(keys::kVersion, "1.0.0.0");
    simple_dict.Set(keys::kName, "unused");
    simple_dict.SetByDottedPath(keys::kLaunchLocalPath, "fake.html");

    std::u16string error;
    app1_scoped_ = Extension::Create(prefs_.temp_dir().AppendASCII("app1_"),
                                     ManifestLocation::kExternalPref,
                                     simple_dict, Extension::NO_FLAGS, &error);
    prefs()->OnExtensionInstalled(app1_scoped_.get(),
                                  /*disable_reasons=*/{},
                                  syncer::StringOrdinal(), std::string());

    app2_scoped_ = Extension::Create(prefs_.temp_dir().AppendASCII("app2_"),
                                     ManifestLocation::kExternalPref,
                                     simple_dict, Extension::NO_FLAGS, &error);
    prefs()->OnExtensionInstalled(app2_scoped_.get(),
                                  /*disable_reasons=*/{},
                                  syncer::StringOrdinal(), std::string());

    app1_ = app1_scoped_.get();
    app2_ = app2_scoped_.get();
  }
  ~ChromeAppSortingPreinstalledAppsBase() override = default;

 protected:
  // Weak references, for convenience.
  raw_ptr<Extension, DanglingUntriaged> app1_;
  raw_ptr<Extension, DanglingUntriaged> app2_;

 private:
  scoped_refptr<Extension> app1_scoped_;
  scoped_refptr<Extension> app2_scoped_;
};

class ChromeAppSortingGetMinOrMaxAppLaunchOrdinalsOnPage
    : public ChromeAppSortingPreinstalledAppsBase {
 public:
  ChromeAppSortingGetMinOrMaxAppLaunchOrdinalsOnPage() = default;
  ~ChromeAppSortingGetMinOrMaxAppLaunchOrdinalsOnPage() override = default;

  void Initialize() override {}
  void Verify() override {
    syncer::StringOrdinal page = syncer::StringOrdinal::CreateInitialOrdinal();

    syncer::StringOrdinal min =
        app_sorting()->GetMinOrMaxAppLaunchOrdinalsOnPage(
            page,
            ChromeAppSorting::MIN_ORDINAL);
    syncer::StringOrdinal max =
        app_sorting()->GetMinOrMaxAppLaunchOrdinalsOnPage(
            page,
            ChromeAppSorting::MAX_ORDINAL);
    EXPECT_TRUE(min.IsValid());
    EXPECT_TRUE(max.IsValid());
    EXPECT_TRUE(min.LessThan(max));

    // Ensure that the min and max values aren't set for empty pages.
    min = syncer::StringOrdinal();
    max = syncer::StringOrdinal();
    syncer::StringOrdinal empty_page = page.CreateAfter();
    EXPECT_FALSE(min.IsValid());
    EXPECT_FALSE(max.IsValid());
    min = app_sorting()->GetMinOrMaxAppLaunchOrdinalsOnPage(
        empty_page,
        ChromeAppSorting::MIN_ORDINAL);
    max = app_sorting()->GetMinOrMaxAppLaunchOrdinalsOnPage(
        empty_page,
        ChromeAppSorting::MAX_ORDINAL);
    EXPECT_FALSE(min.IsValid());
    EXPECT_FALSE(max.IsValid());
  }
};
TEST_F(ChromeAppSortingGetMinOrMaxAppLaunchOrdinalsOnPage,
       ChromeAppSortingGetMinOrMaxAppLaunchOrdinalsOnPage) {}

// Make sure that empty pages aren't removed from the integer to ordinal
// mapping. See http://crbug.com/40136834 for details.
class ChromeAppSortingKeepEmptyStringOrdinalPages
    : public ChromeAppSortingPreinstalledAppsBase {
 public:
  ChromeAppSortingKeepEmptyStringOrdinalPages() = default;
  ~ChromeAppSortingKeepEmptyStringOrdinalPages() override = default;

  void Initialize() override {
    syncer::StringOrdinal first_page =
        syncer::StringOrdinal::CreateInitialOrdinal();
    app_sorting()->SetPageOrdinal(app1_->id(), first_page);
    EXPECT_EQ(0, app_sorting()->PageStringOrdinalAsInteger(first_page));

    last_page_ = first_page.CreateAfter();
    app_sorting()->SetPageOrdinal(app2_->id(), last_page_);
    EXPECT_EQ(1, app_sorting()->PageStringOrdinalAsInteger(last_page_));

    // Move the second app to create an empty page.
    app_sorting()->SetPageOrdinal(app2_->id(), first_page);
    EXPECT_EQ(0, app_sorting()->PageStringOrdinalAsInteger(first_page));
  }
  void Verify() override {
    // Move the second app to a new empty page at the end, skipping over
    // the current empty page.
    last_page_ = last_page_.CreateAfter();
    app_sorting()->SetPageOrdinal(app2_->id(), last_page_);
    EXPECT_EQ(2, app_sorting()->PageStringOrdinalAsInteger(last_page_));
    EXPECT_TRUE(
        last_page_.Equals(app_sorting()->PageIntegerAsStringOrdinal(2)));
  }

 private:
  syncer::StringOrdinal last_page_;
};
TEST_F(ChromeAppSortingKeepEmptyStringOrdinalPages,
       ChromeAppSortingKeepEmptyStringOrdinalPages) {}

class ChromeAppSortingMakesFillerOrdinals
    : public ChromeAppSortingPreinstalledAppsBase {
 public:
  ChromeAppSortingMakesFillerOrdinals() = default;
  ~ChromeAppSortingMakesFillerOrdinals() override = default;

  void Initialize() override {
    syncer::StringOrdinal first_page =
        syncer::StringOrdinal::CreateInitialOrdinal();
    app_sorting()->SetPageOrdinal(app1_->id(), first_page);
    EXPECT_EQ(0, app_sorting()->PageStringOrdinalAsInteger(first_page));
  }
  void Verify() override {
    // Because the UI can add an unlimited number of empty pages without an app
    // on them, this test simulates dropping of an app on the 1st and 4th empty
    // pages (3rd and 6th pages by index) to ensure we don't crash and that
    // filler ordinals are created as needed. See: http://crbug.com/40773838
    syncer::StringOrdinal page_three =
        app_sorting()->PageIntegerAsStringOrdinal(2);
    app_sorting()->SetPageOrdinal(app1_->id(), page_three);
    EXPECT_EQ(2, app_sorting()->PageStringOrdinalAsInteger(page_three));

    syncer::StringOrdinal page_six =
        app_sorting()->PageIntegerAsStringOrdinal(5);
    app_sorting()->SetPageOrdinal(app1_->id(), page_six);
    EXPECT_EQ(5, app_sorting()->PageStringOrdinalAsInteger(page_six));
  }
};
TEST_F(ChromeAppSortingMakesFillerOrdinals,
       ChromeAppSortingMakesFillerOrdinals) {}

class ChromeAppSortingDefaultOrdinalsBase : public ExtensionPrefsTest {
 public:
  ChromeAppSortingDefaultOrdinalsBase() = default;
  ~ChromeAppSortingDefaultOrdinalsBase() override = default;

  void Initialize() override {
    app_ = CreateApp("app");

    InitDefaultOrdinals();
    ChromeAppSorting::AppOrdinalsMap& sorting_defaults =
        app_sorting()->default_ordinals_;
    sorting_defaults[app_->id()].page_ordinal = default_page_ordinal_;
    sorting_defaults[app_->id()].app_launch_ordinal =
        default_app_launch_ordinal_;

    SetupUserOrdinals();
    InstallApps();
  }

 protected:
  scoped_refptr<Extension> CreateApp(const std::string& name) {
    base::DictValue simple_dict;
    simple_dict.Set(keys::kVersion, "1.0.0.0");
    simple_dict.Set(keys::kName, name);
    simple_dict.SetByDottedPath(keys::kLaunchLocalPath, "fake.html");

    std::u16string errors;
    scoped_refptr<Extension> app = Extension::Create(
        prefs_.temp_dir().AppendASCII(name), ManifestLocation::kExternalPref,
        simple_dict, Extension::NO_FLAGS, &errors);
    EXPECT_TRUE(app.get()) << errors;
    EXPECT_TRUE(crx_file::id_util::IdIsValid(app->id()));
    return app;
  }

  void InitDefaultOrdinals() {
    default_page_ordinal_ =
        syncer::StringOrdinal::CreateInitialOrdinal().CreateAfter();
    default_app_launch_ordinal_ =
        syncer::StringOrdinal::CreateInitialOrdinal().CreateBefore();
  }

  virtual void SetupUserOrdinals() {}

  virtual void InstallApps() {
    prefs()->OnExtensionInstalled(app_.get(),
                                  /*disable_reasons=*/{},
                                  syncer::StringOrdinal(), std::string());
  }

  scoped_refptr<Extension> app_;
  syncer::StringOrdinal default_page_ordinal_;
  syncer::StringOrdinal default_app_launch_ordinal_;
};

// Tests that the app gets its default ordinals.
class ChromeAppSortingDefaultOrdinals
    : public ChromeAppSortingDefaultOrdinalsBase {
 public:
  ChromeAppSortingDefaultOrdinals() = default;
  ~ChromeAppSortingDefaultOrdinals() override = default;

  void Verify() override {
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(app_->id()).Equals(
        default_page_ordinal_));
    EXPECT_TRUE(app_sorting()->GetAppLaunchOrdinal(app_->id()).Equals(
        default_app_launch_ordinal_));
  }
};
TEST_F(ChromeAppSortingDefaultOrdinals,
       ChromeAppSortingDefaultOrdinals) {}

// Tests that the default page ordinal is overridden by install page ordinal.
class ChromeAppSortingDefaultOrdinalOverriddenByInstallPage
    : public ChromeAppSortingDefaultOrdinalsBase {
 public:
  ChromeAppSortingDefaultOrdinalOverriddenByInstallPage() = default;
  ~ChromeAppSortingDefaultOrdinalOverriddenByInstallPage() override = default;

  void Verify() override {
    EXPECT_FALSE(app_sorting()->GetPageOrdinal(app_->id()).Equals(
        default_page_ordinal_));
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(app_->id()).Equals(
        install_page_));
  }

 protected:
  void InstallApps() override {
    install_page_ = default_page_ordinal_.CreateAfter();
    prefs()->OnExtensionInstalled(app_.get(),
                                  /*disable_reasons=*/{}, install_page_,
                                  std::string());
  }

 private:
  syncer::StringOrdinal install_page_;
};
TEST_F(ChromeAppSortingDefaultOrdinalOverriddenByInstallPage,
       ChromeAppSortingDefaultOrdinalOverriddenByInstallPage) {}

// Tests that the default ordinals are overridden by user values.
class ChromeAppSortingDefaultOrdinalOverriddenByUserValue
    : public ChromeAppSortingDefaultOrdinalsBase {
 public:
  ChromeAppSortingDefaultOrdinalOverriddenByUserValue() = default;
  ~ChromeAppSortingDefaultOrdinalOverriddenByUserValue() override = default;

  void Verify() override {
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(app_->id()).Equals(
        user_page_ordinal_));
    EXPECT_TRUE(app_sorting()->GetAppLaunchOrdinal(app_->id()).Equals(
        user_app_launch_ordinal_));
  }

 protected:
  void SetupUserOrdinals() override {
    user_page_ordinal_ = default_page_ordinal_.CreateAfter();
    user_app_launch_ordinal_ = default_app_launch_ordinal_.CreateBefore();

    app_sorting()->SetPageOrdinal(app_->id(), user_page_ordinal_);
    app_sorting()->SetAppLaunchOrdinal(app_->id(), user_app_launch_ordinal_);
  }

 private:
  syncer::StringOrdinal user_page_ordinal_;
  syncer::StringOrdinal user_app_launch_ordinal_;
};
TEST_F(ChromeAppSortingDefaultOrdinalOverriddenByUserValue,
       ChromeAppSortingDefaultOrdinalOverriddenByUserValue) {}

// Tests that the default app launch ordinal is changed to avoid collision.
class ChromeAppSortingDefaultOrdinalNoCollision
    : public ChromeAppSortingDefaultOrdinalsBase {
 public:
  ChromeAppSortingDefaultOrdinalNoCollision() = default;
  ~ChromeAppSortingDefaultOrdinalNoCollision() override = default;

  void Verify() override {
    // Use the default page.
    EXPECT_TRUE(app_sorting()->GetPageOrdinal(app_->id()).Equals(
        default_page_ordinal_));
    // Not using the default app launch ordinal because of the collision.
    EXPECT_FALSE(app_sorting()->GetAppLaunchOrdinal(app_->id()).Equals(
        default_app_launch_ordinal_));
  }

 protected:
  void SetupUserOrdinals() override {
    other_app_ = prefs_.AddApp("other_app");
    // Creates a collision.
    app_sorting()->SetPageOrdinal(other_app_->id(), default_page_ordinal_);
    app_sorting()->SetAppLaunchOrdinal(other_app_->id(),
                                       default_app_launch_ordinal_);

    yet_another_app_ = prefs_.AddApp("yet_aother_app");
    app_sorting()->SetPageOrdinal(yet_another_app_->id(),
                                  default_page_ordinal_);
    app_sorting()->SetAppLaunchOrdinal(yet_another_app_->id(),
                                       default_app_launch_ordinal_);
  }

 private:
  scoped_refptr<Extension> other_app_;
  scoped_refptr<Extension> yet_another_app_;
};
TEST_F(ChromeAppSortingDefaultOrdinalNoCollision,
       ChromeAppSortingDefaultOrdinalNoCollision) {}

// Tests that SetExtensionVisible() correctly hides and unhides extensions.
class ChromeAppSortingSetExtensionVisible : public ExtensionPrefsTest {
 public:
  ChromeAppSortingSetExtensionVisible() = default;
  ~ChromeAppSortingSetExtensionVisible() override = default;

  void Initialize() override {
    first_app_ = prefs_.AddApp("first_app");
    second_app_ = prefs_.AddApp("second_app");
  }

  void Verify() override {
    ChromeAppSorting* sorting = app_sorting();
    syncer::StringOrdinal page1 = sorting->GetPageOrdinal(first_app_->id());
    syncer::StringOrdinal page2 = sorting->GetPageOrdinal(second_app_->id());
    EXPECT_TRUE(sorting->GetAppLaunchOrdinal(first_app_->id()).IsValid());
    EXPECT_TRUE(sorting->GetAppLaunchOrdinal(second_app_->id()).IsValid());
    EXPECT_TRUE(page1.IsValid());
    EXPECT_TRUE(page2.IsValid());
    EXPECT_TRUE(page1.Equals(page2));

    sorting->SetExtensionVisible(first_app_->id(), false);
    EXPECT_EQ(
        1U, sorting->CountItemsVisibleOnNtp(sorting->ntp_ordinal_map_[page1]));

    sorting->SetExtensionVisible(first_app_->id(), true);
    EXPECT_EQ(
        2U, sorting->CountItemsVisibleOnNtp(sorting->ntp_ordinal_map_[page1]));
  }

 private:
  scoped_refptr<Extension> first_app_;
  scoped_refptr<Extension> second_app_;
};
TEST_F(ChromeAppSortingSetExtensionVisible,
       ChromeAppSortingSetExtensionVisible) {
}

}  // namespace extensions
