chromium/ash/accelerators/accelerator_lookup_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/accelerators/accelerator_lookup.h"

#include <memory>
#include <vector>

#include "ash/accelerators/ash_accelerator_configuration.h"
#include "ash/keyboard/keyboard_controller_impl.h"
#include "ash/public/cpp/accelerators.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/memory/raw_ptr.h"
#include "base/test/scoped_feature_list.h"
#include "device/udev_linux/fake_udev_loader.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/devices/device_data_manager_test_api.h"
#include "ui/events/devices/input_device.h"
#include "ui/events/devices/keyboard_device.h"
#include "ui/events/keycodes/dom/dom_codes_array.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"

namespace ash {

namespace {

constexpr char kKbdTopRowPropertyName[] = "CROS_KEYBOARD_TOP_ROW_LAYOUT";

constexpr char kKbdTopRowLayout1Tag[] = "1";

using AcceleratorDetails = AcceleratorLookup::AcceleratorDetails;

class FakeDeviceManager {
 public:
  FakeDeviceManager() = default;
  FakeDeviceManager(const FakeDeviceManager&) = delete;
  FakeDeviceManager& operator=(const FakeDeviceManager&) = delete;
  ~FakeDeviceManager() = default;

  // Add a fake keyboard to DeviceDataManagerTestApi and provide layout info to
  // fake udev.
  void AddFakeKeyboard(const ui::KeyboardDevice& fake_keyboard,
                       const std::string& layout) {
    fake_keyboard_devices_.push_back(fake_keyboard);

    ui::DeviceDataManagerTestApi().SetKeyboardDevices({});
    ui::DeviceDataManagerTestApi().SetKeyboardDevices(fake_keyboard_devices_);
    ui::DeviceDataManagerTestApi().OnDeviceListsComplete();

    std::map<std::string, std::string> sysfs_properties;
    std::map<std::string, std::string> sysfs_attributes;
    sysfs_properties[kKbdTopRowPropertyName] = layout;
    fake_udev_.AddFakeDevice(fake_keyboard.name, fake_keyboard.sys_path.value(),
                             /*subsystem=*/"input", /*devnode=*/std::nullopt,
                             /*devtype=*/std::nullopt,
                             std::move(sysfs_attributes),
                             std::move(sysfs_properties));
  }

  void RemoveAllDevices() {
    fake_udev_.Reset();
    fake_keyboard_devices_.clear();
  }

 private:
  testing::FakeUdevLoader fake_udev_;
  std::vector<ui::KeyboardDevice> fake_keyboard_devices_;
};

bool CompareAccelerators(const std::vector<AcceleratorDetails>& expected,
                         const std::vector<AcceleratorDetails>& actual) {
  if (expected.size() != actual.size()) {
    return false;
  }

  for (size_t i = 0; i < expected.size(); ++i) {
    const bool accelerators_equal =
        expected[i].accelerator == actual[i].accelerator;
    const bool key_display_equal =
        expected[i].key_display == actual[i].key_display;
    if (!accelerators_equal || !key_display_equal) {
      return false;
    }
  }

  return true;
}

}  // namespace

class AcceleratorLookupTest : public AshTestBase {
 public:
  AcceleratorLookupTest() = default;
  ~AcceleratorLookupTest() override = default;

  void SetUp() override {
    scoped_feature_list_.InitAndEnableFeature(
        ::features::kShortcutCustomization);
    AshTestBase::SetUp();
    config_ = Shell::Get()->ash_accelerator_configuration();
    accelerator_lookup_ = Shell::Get()->accelerator_lookup();
  }

  void TearDown() override {
    config_ = nullptr;
    accelerator_lookup_ = nullptr;
    AshTestBase::TearDown();
  }

 protected:
  base::test::ScopedFeatureList scoped_feature_list_;
  raw_ptr<AshAcceleratorConfiguration> config_;
  raw_ptr<AcceleratorLookup> accelerator_lookup_;
};

TEST_F(AcceleratorLookupTest, NoAccelerators) {
  config_->Initialize({});

  std::vector<AcceleratorDetails> accelerators =
      accelerator_lookup_->GetAcceleratorsForAction(
          AcceleratorAction::kSwitchToLastUsedIme);

  EXPECT_TRUE(accelerators.empty());
}

TEST_F(AcceleratorLookupTest, LoadAndFetchAccelerator) {
  const AcceleratorData test_data[] = {
      {/*trigger_on_press=*/true, ui::VKEY_A, ui::EF_CONTROL_DOWN,
       AcceleratorAction::kSwitchToLastUsedIme},
  };

  config_->Initialize(test_data);

  const std::vector<ui::Accelerator> expected_accelerators = {
      {ui::VKEY_A, ui::EF_CONTROL_DOWN},
  };

  std::vector<AcceleratorDetails> actual =
      accelerator_lookup_->GetAcceleratorsForAction(
          AcceleratorAction::kSwitchToLastUsedIme);

  std::vector<AcceleratorDetails> expected = {
      {{ui::VKEY_A, ui::EF_CONTROL_DOWN}, std::u16string(u"a")},
  };

  EXPECT_TRUE(CompareAccelerators(expected, actual));
}

TEST_F(AcceleratorLookupTest, ModifiedAccelerator) {
  const AcceleratorData test_data[] = {
      {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
       AcceleratorAction::kSwitchToLastUsedIme},
  };

  config_->Initialize(test_data);

  std::vector<AcceleratorDetails> expected = {
      {{ui::VKEY_SPACE, ui::EF_CONTROL_DOWN}, std::u16string(u"space")},
  };

  std::vector<AcceleratorDetails> actual =
      accelerator_lookup_->GetAcceleratorsForAction(
          AcceleratorAction::kSwitchToLastUsedIme);

  EXPECT_TRUE(CompareAccelerators(expected, actual));

  config_->AddUserAccelerator(AcceleratorAction::kSwitchToLastUsedIme,
                              {ui::VKEY_A, ui::EF_COMMAND_DOWN});

  expected = {
      {{ui::VKEY_SPACE, ui::EF_CONTROL_DOWN}, std::u16string(u"space")},
      {{ui::VKEY_A, ui::EF_COMMAND_DOWN}, std::u16string(u"a")},
  };

  actual = accelerator_lookup_->GetAcceleratorsForAction(
      AcceleratorAction::kSwitchToLastUsedIme);
  EXPECT_TRUE(CompareAccelerators(expected, actual));
}

TEST_F(AcceleratorLookupTest, RemovedAccelerator) {
  const AcceleratorData test_data[] = {
      {/*trigger_on_press=*/true, ui::VKEY_SPACE, ui::EF_CONTROL_DOWN,
       AcceleratorAction::kSwitchToLastUsedIme},
  };

  config_->Initialize(test_data);
  config_->RemoveAccelerator(AcceleratorAction::kSwitchToLastUsedIme,
                             {ui::VKEY_SPACE, ui::EF_CONTROL_DOWN});

  std::vector<AcceleratorDetails> accelerators =
      accelerator_lookup_->GetAcceleratorsForAction(
          AcceleratorAction::kBrightnessDown);

  EXPECT_TRUE(accelerators.empty());
}

TEST_F(AcceleratorLookupTest, FilteredAccelerators) {
  std::unique_ptr<FakeDeviceManager> fake_keyboard_manager_ =
      std::make_unique<FakeDeviceManager>();
  ui::KeyboardDevice fake_keyboard(
      /*id=*/1, /*type=*/ui::InputDeviceType::INPUT_DEVICE_INTERNAL,
      /*name=*/kKbdTopRowLayout1Tag);
  fake_keyboard.sys_path = base::FilePath("path");
  fake_keyboard_manager_->AddFakeKeyboard(fake_keyboard, kKbdTopRowLayout1Tag);

  const AcceleratorData test_data[] = {
      {/*trigger_on_press=*/true, ui::VKEY_HELP, ui::EF_NONE,
       AcceleratorAction::kShowShortcutViewer},
      {/*trigger_on_press=*/true, ui::VKEY_S,
       ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN,
       AcceleratorAction::kShowShortcutViewer},
  };

  config_->Initialize(test_data);

  std::vector<AcceleratorDetails> actual =
      accelerator_lookup_->GetAvailableAcceleratorsForAction(
          AcceleratorAction::kShowShortcutViewer);

  std::vector<AcceleratorDetails> expected = {
      {{ui::VKEY_S, ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN},
       std::u16string(u"s")},
  };

  // Expect that the HELP key shortcut is filtered since this is an internal
  // keyboard.
  EXPECT_EQ(1u, actual.size());
  EXPECT_TRUE(CompareAccelerators(expected, actual));
}

class AcceleratorDetailsTextTest
    : public AcceleratorLookupTest,
      public testing::WithParamInterface<
          std::tuple<AcceleratorDetails, std::u16string>> {
 public:
  void SetUp() override {
    AcceleratorLookupTest::SetUp();
    std::tie(details_, expected_) = GetParam();
  }

 protected:
  AcceleratorDetails details_;
  std::u16string expected_;
};

INSTANTIATE_TEST_SUITE_P(
    // Empty to simplify gtest output
    ,
    AcceleratorDetailsTextTest,
    testing::ValuesIn(std::vector<
                      std::tuple<AcceleratorDetails, std::u16string>>{
        {{ui::Accelerator(ui::VKEY_A, ui::EF_NONE), u"a"}, u"a"},
        {{ui::Accelerator(ui::VKEY_BROWSER_REFRESH, ui::EF_NONE),
          u"browserRefresh"},
         u"browserRefresh"},
        {{ui::Accelerator(ui::VKEY_A, ui::EF_CONTROL_DOWN), u"a"}, u"ctrl+a"},
        {{ui::Accelerator(ui::VKEY_A, ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN),
          u"a"},
         u"ctrl+alt+a"},
        {{ui::Accelerator(ui::VKEY_A,
                          ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN |
                              ui::EF_SHIFT_DOWN | ui::EF_COMMAND_DOWN),
          u"a"},
         u"search+ctrl+alt+shift+a"},
        {{ui::Accelerator(ui::VKEY_A, ui::EF_COMMAND_DOWN), u"a"}, u"search+a"},
    }));

TEST_P(AcceleratorDetailsTextTest, ExpectedText) {
  const std::u16string actual =
      AcceleratorLookup::GetAcceleratorDetailsText(details_);
  EXPECT_EQ(expected_, actual);
}

}  // namespace ash