| // 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 "device/udev_linux/fake_udev_loader.h" |
| #include "testing/gtest/include/gtest/gtest.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 { |
| 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: |
| 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 |