chromium/ash/system/input_device_settings/pref_handlers/pointing_stick_pref_handler_unittest.cc

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

#include "ash/public/mojom/input_device_settings.mojom-forward.h"
#include "ash/system/input_device_settings/pref_handlers/pointing_stick_pref_handler_impl.h"

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/shell.h"
#include "ash/system/input_device_settings/input_device_settings_defaults.h"
#include "ash/system/input_device_settings/input_device_settings_pref_names.h"
#include "ash/system/input_device_settings/input_device_tracker.h"
#include "ash/system/input_device_settings/settings_updated_metrics_info.h"
#include "ash/test/ash_test_base.h"
#include "base/test/scoped_feature_list.h"
#include "components/account_id/account_id.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/user_manager/known_user.h"

namespace ash {

namespace {
const std::string kDictFakeKey = "fake_key";
const std::string kDictFakeValue = "fake_value";

const std::string kPointingStickKey1 = "device_key1";
const std::string kPointingStickKey2 = "device_key2";

constexpr char kUserEmail[] = "[email protected]";
constexpr char kUserEmail2[] = "[email protected]";
const AccountId account_id_1 = AccountId::FromUserEmail(kUserEmail);
const AccountId account_id_2 = AccountId::FromUserEmail(kUserEmail2);

const int kTestSensitivity = 2;
const bool kTestSwapRight = false;
const bool kTestAccelerationEnabled = false;

const mojom::PointingStickSettings kPointingStickSettingsDefault(
    /*swap_right=*/kDefaultSwapRight,
    /*sensitivity=*/kDefaultSensitivity,
    /*acceleration_enabled=*/kDefaultAccelerationEnabled);

const mojom::PointingStickSettings kPointingStickSettingsNotDefault(
    /*swap_right=*/!kDefaultSwapRight,
    /*sensitivity=*/1,
    /*acceleration_enabled=*/!kDefaultAccelerationEnabled);

const mojom::PointingStickSettings kPointingStickSettings1(
    /*swap_right=*/true,
    /*sensitivity=*/1,
    /*acceleration_enabled=*/false);

const mojom::PointingStickSettings kPointingStickSettings2(
    /*swap_right=*/false,
    /*sensitivity=*/3,
    /*acceleration_enabled=*/true);

}  // namespace

class PointingStickPrefHandlerTest : public AshTestBase {
 public:
  PointingStickPrefHandlerTest() = default;
  PointingStickPrefHandlerTest(const PointingStickPrefHandlerTest&) = delete;
  PointingStickPrefHandlerTest& operator=(const PointingStickPrefHandlerTest&) =
      delete;
  ~PointingStickPrefHandlerTest() override = default;

  // testing::Test:
  void SetUp() override {
    scoped_feature_list_.InitAndEnableFeature(
        features::kInputDeviceSettingsSplit);
    AshTestBase::SetUp();
    InitializePrefService();
    pref_handler_ = std::make_unique<PointingStickPrefHandlerImpl>();
  }

  void TearDown() override {
    pref_handler_.reset();
    AshTestBase::TearDown();
  }

  void InitializePrefService() {
    local_state()->registry()->RegisterBooleanPref(
        prefs::kOwnerPrimaryPointingStickButtonRight, /*default_value=*/false);
    pref_service_ = std::make_unique<TestingPrefServiceSimple>();

    pref_service_->registry()->RegisterDictionaryPref(
        prefs::kPointingStickDeviceSettingsDictPref);
    pref_service_->registry()->RegisterDictionaryPref(
        prefs::kPointingStickInternalSettings);
    pref_service_->registry()->RegisterDictionaryPref(
        prefs::kPointingStickUpdateSettingsMetricInfo);

    pref_service_->registry()->RegisterIntegerPref(
        prefs::kPointingStickSensitivity, kDefaultSensitivity);
    pref_service_->registry()->RegisterBooleanPref(
        prefs::kPrimaryPointingStickButtonRight, kDefaultSwapRight);
    pref_service_->registry()->RegisterBooleanPref(
        prefs::kPointingStickAcceleration, kDefaultAccelerationEnabled);

    pref_service_->SetUserPref(prefs::kPointingStickSensitivity,
                               base::Value(kTestSensitivity));
    pref_service_->SetUserPref(prefs::kPrimaryPointingStickButtonRight,
                               base::Value(kTestSwapRight));
    pref_service_->SetUserPref(prefs::kPointingStickAcceleration,
                               base::Value(kTestAccelerationEnabled));
  }

  void CheckPointingStickSettingsAndDictAreEqual(
      const mojom::PointingStickSettings& settings,
      const base::Value::Dict& settings_dict) {
    const auto sensitivity =
        settings_dict.FindInt(prefs::kPointingStickSettingSensitivity);
    if (sensitivity.has_value()) {
      EXPECT_EQ(settings.sensitivity, sensitivity);
    } else {
      EXPECT_EQ(settings.sensitivity, kDefaultSensitivity);
    }

    const auto swap_right =
        settings_dict.FindBool(prefs::kPointingStickSettingSwapRight);
    if (swap_right.has_value()) {
      EXPECT_EQ(settings.swap_right, swap_right);
    } else {
      EXPECT_EQ(settings.swap_right, kDefaultSwapRight);
    }

    const auto acceleration_enabled =
        settings_dict.FindBool(prefs::kPointingStickSettingAcceleration);
    if (acceleration_enabled.has_value()) {
      EXPECT_EQ(settings.acceleration_enabled, acceleration_enabled);
    } else {
      EXPECT_EQ(settings.acceleration_enabled, kDefaultAccelerationEnabled);
    }
  }

  void CheckPointingStickSettingsAreSetToDefaultValues(
      const mojom::PointingStickSettings& settings) {
    EXPECT_EQ(kPointingStickSettingsDefault.swap_right, settings.swap_right);
    EXPECT_EQ(kPointingStickSettingsDefault.sensitivity, settings.sensitivity);
    EXPECT_EQ(kPointingStickSettingsDefault.acceleration_enabled,
              settings.acceleration_enabled);
  }

  void CallUpdatePointingStickSettings(
      const std::string& device_key,
      const mojom::PointingStickSettings& settings,
      bool is_external = true) {
    mojom::PointingStickPtr pointing_stick = mojom::PointingStick::New();
    pointing_stick->settings = settings.Clone();
    pointing_stick->device_key = device_key;
    pointing_stick->is_external = is_external;

    pref_handler_->UpdatePointingStickSettings(pref_service_.get(),
                                               *pointing_stick);
  }

  void CallUpdateLoginScreenPointingStickSettings(
      const AccountId& account_id,
      const std::string& device_key,
      const mojom::PointingStickSettings& settings) {
    mojom::PointingStickPtr pointing_stick = mojom::PointingStick::New();
    pointing_stick->settings = settings.Clone();
    pref_handler_->UpdateLoginScreenPointingStickSettings(
        local_state(), account_id, *pointing_stick);
  }

  mojom::PointingStickSettingsPtr CallInitializePointingStickSettings(
      const std::string& device_key,
      bool is_external = true) {
    mojom::PointingStickPtr pointing_stick = mojom::PointingStick::New();
    pointing_stick->device_key = device_key;
    pointing_stick->is_external = is_external;

    pref_handler_->InitializePointingStickSettings(pref_service_.get(),
                                                   pointing_stick.get());
    return std::move(pointing_stick->settings);
  }

  mojom::PointingStickSettingsPtr
  CallInitializeLoginScreenPointingStickSettings(
      const AccountId& account_id,
      const mojom::PointingStick& pointing_stick) {
    const auto pointing_stick_ptr = pointing_stick.Clone();

    pref_handler_->InitializeLoginScreenPointingStickSettings(
        local_state(), account_id, pointing_stick_ptr.get());
    return std::move(pointing_stick_ptr->settings);
  }

  const base::Value::Dict* GetSettingsDict(const std::string& device_key,
                                           bool is_external = true) {
    if (!is_external) {
      return &pref_service_->GetDict(prefs::kPointingStickInternalSettings);
    }

    const auto& devices_dict =
        pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref);
    EXPECT_EQ(1u, devices_dict.size());
    const auto* settings_dict = devices_dict.FindDict(device_key);
    EXPECT_NE(nullptr, settings_dict);

    return settings_dict;
  }

  user_manager::KnownUser known_user() {
    return user_manager::KnownUser(local_state());
  }

  bool HasInternalLoginScreenSettingsDict(AccountId account_id) {
    const auto* dict = known_user().FindPath(
        account_id, prefs::kPointingStickLoginScreenInternalSettingsPref);
    return dict && dict->is_dict();
  }

  bool HasExternalLoginScreenSettingsDict(AccountId account_id) {
    const auto* dict = known_user().FindPath(
        account_id, prefs::kPointingStickLoginScreenExternalSettingsPref);
    return dict && dict->is_dict();
  }

  base::Value::Dict GetInternalLoginScreenSettingsDict(AccountId account_id) {
    return known_user()
        .FindPath(account_id,
                  prefs::kPointingStickLoginScreenInternalSettingsPref)
        ->GetDict()
        .Clone();
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
  std::unique_ptr<PointingStickPrefHandlerImpl> pref_handler_;
  std::unique_ptr<TestingPrefServiceSimple> pref_service_;
};

TEST_F(PointingStickPrefHandlerTest,
       InitializeLoginScreenPointingStickSettings) {
  mojom::PointingStick pointing_stick;
  pointing_stick.device_key = kPointingStickKey1;
  pointing_stick.is_external = false;
  mojom::PointingStickSettingsPtr settings =
      CallInitializeLoginScreenPointingStickSettings(account_id_1,
                                                     pointing_stick);

  EXPECT_FALSE(HasInternalLoginScreenSettingsDict(account_id_1));
  CheckPointingStickSettingsAreSetToDefaultValues(*settings);
}

TEST_F(PointingStickPrefHandlerTest, UpdateLoginScreenPointingStickSettings) {
  mojom::PointingStick PointingStick;
  PointingStick.device_key = kPointingStickKey1;
  PointingStick.is_external = false;
  mojom::PointingStickSettingsPtr settings =
      CallInitializeLoginScreenPointingStickSettings(account_id_1,
                                                     PointingStick);
  mojom::PointingStickSettings updated_settings = *settings;
  updated_settings.swap_right = !updated_settings.swap_right;
  updated_settings.acceleration_enabled =
      !updated_settings.acceleration_enabled;
  CallUpdateLoginScreenPointingStickSettings(account_id_1, kPointingStickKey1,
                                             updated_settings);
  const auto& updated_settings_dict =
      GetInternalLoginScreenSettingsDict(account_id_1);
  CheckPointingStickSettingsAndDictAreEqual(updated_settings,
                                            updated_settings_dict);
}

TEST_F(PointingStickPrefHandlerTest,
       LoginScreenPrefsNotPersistedWhenFlagIsDisabled) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(features::kInputDeviceSettingsSplit);
  mojom::PointingStick pointing_stick1;
  pointing_stick1.device_key = kPointingStickKey1;
  pointing_stick1.is_external = false;
  mojom::PointingStick pointing_stick2;
  pointing_stick2.device_key = kPointingStickKey2;
  pointing_stick2.is_external = true;
  CallInitializeLoginScreenPointingStickSettings(account_id_1, pointing_stick1);
  CallInitializeLoginScreenPointingStickSettings(account_id_1, pointing_stick2);
  EXPECT_FALSE(HasInternalLoginScreenSettingsDict(account_id_1));
  EXPECT_FALSE(HasExternalLoginScreenSettingsDict(account_id_1));
}

TEST_F(PointingStickPrefHandlerTest, MultipleDevices) {
  CallUpdatePointingStickSettings(kPointingStickKey1, kPointingStickSettings1);
  CallUpdatePointingStickSettings(kPointingStickKey2, kPointingStickSettings2);

  const auto& devices_dict =
      pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref);
  ASSERT_EQ(2u, devices_dict.size());

  auto* settings_dict = devices_dict.FindDict(kPointingStickKey1);
  ASSERT_NE(nullptr, settings_dict);
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettings1,
                                            *settings_dict);

  settings_dict = devices_dict.FindDict(kPointingStickKey2);
  ASSERT_NE(nullptr, settings_dict);
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettings2,
                                            *settings_dict);
}

TEST_F(PointingStickPrefHandlerTest, PreservesOldSettings) {
  CallUpdatePointingStickSettings(kPointingStickKey1, kPointingStickSettings1);

  auto devices_dict =
      pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref)
          .Clone();
  auto* settings_dict = devices_dict.FindDict(kPointingStickKey1);
  ASSERT_NE(nullptr, settings_dict);

  // Set a fake key to simulate a setting being removed from 1 milestone to the
  // next.
  settings_dict->Set(kDictFakeKey, kDictFakeValue);
  pref_service_->SetDict(prefs::kPointingStickDeviceSettingsDictPref,
                         std::move(devices_dict));

  // Update the settings again and verify the fake key and value still exist.
  CallUpdatePointingStickSettings(kPointingStickKey1, kPointingStickSettings1);

  const auto& updated_devices_dict =
      pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref);
  const auto* updated_settings_dict =
      updated_devices_dict.FindDict(kPointingStickKey1);

  const std::string* value = updated_settings_dict->FindString(kDictFakeKey);
  ASSERT_NE(nullptr, value);
  EXPECT_EQ(kDictFakeValue, *value);
}

TEST_F(PointingStickPrefHandlerTest, LastUpdated) {
  CallUpdatePointingStickSettings(kPointingStickKey1, kPointingStickSettings1,
                                  /*is_external=*/true);
  auto devices_dict =
      pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref)
          .Clone();
  auto* settings_dict = devices_dict.FindDict(kPointingStickKey1);
  ASSERT_NE(nullptr, settings_dict);
  auto* time_stamp1 = settings_dict->Find(prefs::kLastUpdatedKey);
  ASSERT_NE(nullptr, time_stamp1);

  mojom::PointingStickSettingsPtr updated_settings =
      kPointingStickSettings1.Clone();
  updated_settings->swap_right = !updated_settings->swap_right;
  CallUpdatePointingStickSettings(kPointingStickKey1, *updated_settings);

  const auto& updated_devices_dict =
      pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref);
  const auto* updated_settings_dict =
      updated_devices_dict.FindDict(kPointingStickKey1);
  ASSERT_NE(nullptr, updated_settings_dict);
  auto* updated_time_stamp1 =
      updated_settings_dict->Find(prefs::kLastUpdatedKey);
  ASSERT_NE(nullptr, updated_time_stamp1);
  ASSERT_NE(time_stamp1, updated_time_stamp1);
}

TEST_F(PointingStickPrefHandlerTest, UpdateSettings) {
  CallUpdatePointingStickSettings(kPointingStickKey1, kPointingStickSettings1);
  CallUpdatePointingStickSettings(kPointingStickKey2, kPointingStickSettings2);

  auto devices_dict =
      pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref)
          .Clone();
  auto* settings_dict = devices_dict.FindDict(kPointingStickKey1);
  ASSERT_NE(nullptr, settings_dict);
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettings1,
                                            *settings_dict);

  settings_dict = devices_dict.FindDict(kPointingStickKey2);
  ASSERT_NE(nullptr, settings_dict);
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettings2,
                                            *settings_dict);

  mojom::PointingStickSettings updated_settings = kPointingStickSettings1;
  updated_settings.swap_right = !updated_settings.swap_right;

  // Update the settings again and verify the settings are updated in place.
  CallUpdatePointingStickSettings(kPointingStickKey1, updated_settings);

  const auto& updated_devices_dict =
      pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref);
  const auto* updated_settings_dict =
      updated_devices_dict.FindDict(kPointingStickKey1);
  ASSERT_NE(nullptr, updated_settings_dict);
  CheckPointingStickSettingsAndDictAreEqual(updated_settings,
                                            *updated_settings_dict);

  // Verify other device remains unmodified.
  const auto* unchanged_settings_dict =
      updated_devices_dict.FindDict(kPointingStickKey2);
  ASSERT_NE(nullptr, unchanged_settings_dict);
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettings2,
                                            *unchanged_settings_dict);
}

TEST_F(PointingStickPrefHandlerTest, UpdateSettingsInternal) {
  CallUpdatePointingStickSettings(kPointingStickKey1, kPointingStickSettings1,
                                  /*is_external=*/false);

  const auto& settings_dict =
      pref_service_->GetDict(prefs::kPointingStickInternalSettings);
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettings1,
                                            settings_dict);

  mojom::PointingStickSettings updated_settings = kPointingStickSettings1;
  updated_settings.swap_right = !updated_settings.swap_right;

  // Update the settings again and verify the settings are updated in place.
  CallUpdatePointingStickSettings(kPointingStickKey1, updated_settings,
                                  /*is_external=*/false);

  const auto& updated_settings_dict =
      pref_service_->GetDict(prefs::kPointingStickInternalSettings);
  CheckPointingStickSettingsAndDictAreEqual(updated_settings,
                                            updated_settings_dict);
}

TEST_F(PointingStickPrefHandlerTest, NewSettingAddedRoundTrip) {
  mojom::PointingStickSettings test_settings = kPointingStickSettings1;
  test_settings.swap_right = !kDefaultSwapRight;

  CallUpdatePointingStickSettings(kPointingStickKey1, test_settings);
  auto devices_dict =
      pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref)
          .Clone();
  auto* settings_dict = devices_dict.FindDict(kPointingStickKey1);

  // Remove key from the dict to mock adding a new setting in the future.
  settings_dict->Remove(prefs::kPointingStickSettingSwapRight);
  pref_service_->SetDict(prefs::kPointingStickDeviceSettingsDictPref,
                         std::move(devices_dict));

  // Initialize keyboard settings for the device and check that
  // "new settings" match their default values.
  mojom::PointingStickSettingsPtr settings =
      CallInitializePointingStickSettings(kPointingStickKey1);
  EXPECT_EQ(kDefaultSwapRight, settings->swap_right);

  // Reset "new settings" to the values that match `test_settings` and check
  // that the rest of the fields are equal.
  settings->swap_right = !kDefaultSwapRight;
  EXPECT_EQ(test_settings, *settings);
}

TEST_F(PointingStickPrefHandlerTest, NewSettingAddedRoundTripInternal) {
  mojom::PointingStickSettings test_settings = kPointingStickSettings1;
  test_settings.swap_right = !kDefaultSwapRight;

  CallUpdatePointingStickSettings(kPointingStickKey1, test_settings,
                                  /*is_external=*/false);
  auto settings_dict =
      pref_service_->GetDict(prefs::kPointingStickInternalSettings).Clone();

  // Remove key from the dict to mock adding a new setting in the future.
  settings_dict.Remove(prefs::kPointingStickSettingSwapRight);
  pref_service_->SetDict(prefs::kPointingStickInternalSettings,
                         std::move(settings_dict));

  // Initialize PointingStick settings for the device and check that
  // "new settings" match their default values.
  mojom::PointingStickSettingsPtr settings =
      CallInitializePointingStickSettings(kPointingStickKey1,
                                          /*is_external=*/false);
  EXPECT_EQ(kDefaultReverseScrolling, settings->swap_right);

  // Reset "new settings" to the values that match `test_settings` and check
  // that the rest of the fields are equal.
  settings->swap_right = !kDefaultReverseScrolling;
  EXPECT_EQ(test_settings, *settings);
}

TEST_F(PointingStickPrefHandlerTest, DefaultSettingsWhenPrefServiceNull) {
  mojom::PointingStick pointing_stick;
  pointing_stick.device_key = kPointingStickKey1;
  pref_handler_->InitializePointingStickSettings(nullptr, &pointing_stick);
  EXPECT_EQ(kPointingStickSettingsDefault, *pointing_stick.settings);
}

TEST_F(PointingStickPrefHandlerTest, NewPointingStickDefaultSettings) {
  mojom::PointingStickSettingsPtr settings =
      CallInitializePointingStickSettings(kPointingStickKey1);
  EXPECT_EQ(*settings, kPointingStickSettingsDefault);
  settings = CallInitializePointingStickSettings(kPointingStickKey2);
  EXPECT_EQ(*settings, kPointingStickSettingsDefault);

  auto devices_dict =
      pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref)
          .Clone();
  ASSERT_EQ(2u, devices_dict.size());
  auto* settings_dict = devices_dict.FindDict(kPointingStickKey1);
  ASSERT_NE(nullptr, settings_dict);
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettingsDefault,
                                            *settings_dict);

  settings_dict = devices_dict.FindDict(kPointingStickKey2);
  ASSERT_NE(nullptr, settings_dict);
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettingsDefault,
                                            *settings_dict);
}

TEST_F(PointingStickPrefHandlerTest, NewPointingStickDefaultSettingsInternal) {
  mojom::PointingStickSettingsPtr settings =
      CallInitializePointingStickSettings(kPointingStickKey1,
                                          /*is_external=*/false);
  EXPECT_EQ(*settings, kPointingStickSettingsDefault);

  auto& settings_dict =
      pref_service_->GetDict(prefs::kPointingStickInternalSettings);
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettingsDefault,
                                            settings_dict);
}

TEST_F(PointingStickPrefHandlerTest,
       PointingStickObserveredInTransitionPeriod) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(features::kInputDeviceSettingsSplit);
  mojom::PointingStick pointing_stick;
  pointing_stick.device_key = kPointingStickKey1;
  Shell::Get()->input_device_tracker()->OnPointingStickConnected(
      pointing_stick);
  // Initialize PointingStick settings for the device and check that the global
  // prefs were used as defaults.
  mojom::PointingStickSettingsPtr settings =
      CallInitializePointingStickSettings(pointing_stick.device_key);
  ASSERT_EQ(settings->sensitivity, kTestSensitivity);
  ASSERT_EQ(settings->swap_right, kTestSwapRight);
  ASSERT_EQ(settings->acceleration_enabled, kTestAccelerationEnabled);
}

TEST_F(PointingStickPrefHandlerTest,
       TransitionPeriodSettingsPersistedWhenUserChosen) {
  base::test::ScopedFeatureList feature_list;
  feature_list.InitAndDisableFeature(features::kInputDeviceSettingsSplit);
  mojom::PointingStick pointing_stick;
  pointing_stick.device_key = kPointingStickKey1;
  Shell::Get()->input_device_tracker()->OnPointingStickConnected(
      pointing_stick);

  pref_service_->SetUserPref(prefs::kPointingStickSensitivity,
                             base::Value(kDefaultSensitivity));
  pref_service_->SetUserPref(prefs::kPrimaryPointingStickButtonRight,
                             base::Value(kDefaultSwapRight));
  pref_service_->SetUserPref(prefs::kPointingStickAcceleration,
                             base::Value(kDefaultAccelerationEnabled));
  mojom::PointingStickSettingsPtr settings =
      CallInitializePointingStickSettings(pointing_stick.device_key);
  EXPECT_EQ(kPointingStickSettingsDefault, *settings);

  const auto* settings_dict = GetSettingsDict(pointing_stick.device_key);
  EXPECT_TRUE(settings_dict->contains(prefs::kPointingStickSettingSensitivity));
  EXPECT_TRUE(
      settings_dict->contains(prefs::kPointingStickSettingAcceleration));
  EXPECT_TRUE(settings_dict->contains(prefs::kPointingStickSettingSwapRight));
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettingsDefault,
                                            *settings_dict);
}

TEST_F(PointingStickPrefHandlerTest, DefaultNotPersistedUntilUpdated) {
  CallUpdatePointingStickSettings(kPointingStickKey1,
                                  kPointingStickSettingsDefault);

  const auto* settings_dict = GetSettingsDict(kPointingStickKey1);
  EXPECT_FALSE(
      settings_dict->contains(prefs::kPointingStickSettingSensitivity));
  EXPECT_FALSE(
      settings_dict->contains(prefs::kPointingStickSettingAcceleration));
  EXPECT_FALSE(settings_dict->contains(prefs::kPointingStickSettingSwapRight));
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettingsDefault,
                                            *settings_dict);

  CallUpdatePointingStickSettings(kPointingStickKey1,
                                  kPointingStickSettingsNotDefault);
  settings_dict = GetSettingsDict(kPointingStickKey1);
  EXPECT_TRUE(settings_dict->contains(prefs::kPointingStickSettingSensitivity));
  EXPECT_TRUE(
      settings_dict->contains(prefs::kPointingStickSettingAcceleration));
  EXPECT_TRUE(settings_dict->contains(prefs::kPointingStickSettingSwapRight));
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettingsNotDefault,
                                            *settings_dict);

  CallUpdatePointingStickSettings(kPointingStickKey1,
                                  kPointingStickSettingsDefault);
  settings_dict = GetSettingsDict(kPointingStickKey1);
  EXPECT_TRUE(settings_dict->contains(prefs::kPointingStickSettingSensitivity));
  EXPECT_TRUE(
      settings_dict->contains(prefs::kPointingStickSettingAcceleration));
  EXPECT_TRUE(settings_dict->contains(prefs::kPointingStickSettingSwapRight));
  CheckPointingStickSettingsAndDictAreEqual(kPointingStickSettingsDefault,
                                            *settings_dict);
}

TEST_F(PointingStickPrefHandlerTest, SettingsUpdateMetricTest) {
  const auto settings1 =
      CallInitializePointingStickSettings(kPointingStickKey1);

  // When its the first device of the type the category should be kFirstEver.
  {
    const auto& metric_dict =
        pref_service_->GetDict(prefs::kPointingStickUpdateSettingsMetricInfo);
    ASSERT_TRUE(metric_dict.contains(kPointingStickKey1));

    auto metrics_info = SettingsUpdatedMetricsInfo::FromDict(
        *metric_dict.FindDict(kPointingStickKey1));
    ASSERT_TRUE(metrics_info);
    EXPECT_EQ(SettingsUpdatedMetricsInfo::Category::kFirstEver,
              metrics_info->category());
  }

  // When its taken from synced prefs on a different device, category should
  // match.
  {
    auto devices_dict =
        pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref)
            .Clone();
    devices_dict.Set(kPointingStickKey2, base::Value::Dict());
    pref_service_->SetDict(prefs::kPointingStickDeviceSettingsDictPref,
                           std::move(devices_dict));

    CallInitializePointingStickSettings(kPointingStickKey2);
    const auto& metric_dict =
        pref_service_->GetDict(prefs::kPointingStickUpdateSettingsMetricInfo);
    ASSERT_TRUE(metric_dict.contains(kPointingStickKey2));

    auto metrics_info = SettingsUpdatedMetricsInfo::FromDict(
        *metric_dict.FindDict(kPointingStickKey2));
    ASSERT_TRUE(metrics_info);
    EXPECT_EQ(SettingsUpdatedMetricsInfo::Category::kSynced,
              metrics_info->category());
  }
}

class PointingStickSettingsPrefConversionTest
    : public PointingStickPrefHandlerTest,
      public testing::WithParamInterface<
          std::tuple<std::string, mojom::PointingStickSettings>> {
 public:
  PointingStickSettingsPrefConversionTest() = default;
  PointingStickSettingsPrefConversionTest(
      const PointingStickSettingsPrefConversionTest&) = delete;
  PointingStickSettingsPrefConversionTest& operator=(
      const PointingStickSettingsPrefConversionTest&) = delete;
  ~PointingStickSettingsPrefConversionTest() override = default;

  // testing::Test:
  void SetUp() override {
    PointingStickPrefHandlerTest::SetUp();
    std::tie(device_key_, settings_) = GetParam();
  }

 protected:
  std::string device_key_;
  mojom::PointingStickSettings settings_;
};

INSTANTIATE_TEST_SUITE_P(
    // Empty to simplify gtest output
    ,
    PointingStickSettingsPrefConversionTest,
    testing::Combine(testing::Values(kPointingStickKey1, kPointingStickKey2),
                     testing::Values(kPointingStickSettings1,
                                     kPointingStickSettings2)));

TEST_P(PointingStickSettingsPrefConversionTest, CheckConversion) {
  CallUpdatePointingStickSettings(device_key_, settings_);

  const auto& devices_dict =
      pref_service_->GetDict(prefs::kPointingStickDeviceSettingsDictPref);
  ASSERT_EQ(1u, devices_dict.size());
  auto* settings_dict = devices_dict.FindDict(device_key_);
  ASSERT_NE(nullptr, settings_dict);

  CheckPointingStickSettingsAndDictAreEqual(settings_, *settings_dict);
}

TEST_P(PointingStickSettingsPrefConversionTest, CheckConversionInternal) {
  CallUpdatePointingStickSettings(device_key_, settings_,
                                  /*is_external=*/false);

  const auto* settings_dict =
      GetSettingsDict(device_key_, /*is_external=*/false);
  CheckPointingStickSettingsAndDictAreEqual(settings_, *settings_dict);
}

}  // namespace ash