chromium/chrome/browser/ui/webui/ash/settings/pages/a11y/switch_access_handler_browsertest.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 "chrome/browser/ui/webui/ash/settings/pages/a11y/switch_access_handler.h"

#include "base/test/bind.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_web_ui.h"
#include "ui/events/event.h"
#include "ui/events/keycodes/keyboard_codes.h"
#include "ui/events/types/event_type.h"
#include "url/gurl.h"

namespace ash::settings {

class TestSwitchAccessHandler : public SwitchAccessHandler {
 public:
  explicit TestSwitchAccessHandler(content::TestWebUI* web_ui,
                                   PrefService* pref_service)
      : SwitchAccessHandler(pref_service) {
    set_web_ui(web_ui);
    skip_pre_target_handler_for_testing_ = true;
  }

  void set_on_listeners_added(base::OnceClosure callback) {
    on_pre_target_handler_added_for_testing_ = std::move(callback);
  }

  ~TestSwitchAccessHandler() override = default;
};

class SwitchAccessHandlerTest : public InProcessBrowserTest {
 public:
  SwitchAccessHandlerTest() = default;
  ~SwitchAccessHandlerTest() override = default;
  SwitchAccessHandlerTest(const SwitchAccessHandlerTest&) = delete;
  SwitchAccessHandlerTest& operator=(const SwitchAccessHandlerTest&) = delete;

  void SetUpOnMainThread() override {
    handler_ = std::make_unique<TestSwitchAccessHandler>(
        &web_ui_, browser()->profile()->GetPrefs());
  }

  void TearDownOnMainThread() override { handler_.reset(); }

  void SetPaneActive() {
    handler()->HandleNotifySwitchAccessActionAssignmentPaneActive(/*args=*/{});
  }

  void SetPaneInactive() {
    handler()->HandleNotifySwitchAccessActionAssignmentPaneInactive(
        /*args=*/{});
  }

  content::TestWebUI* web_ui() { return &web_ui_; }
  TestSwitchAccessHandler* handler() { return handler_.get(); }

  void AssertKeyPressData(ui::KeyboardCode code, const std::string& name) {
    auto* call_data =
        FindCallForEvent("switch-access-got-key-press-for-assignment");
    ASSERT_NE(call_data, nullptr);
    ASSERT_EQ(call_data->args().size(), 2u);

    ASSERT_TRUE(call_data->arg2()->is_dict());
    auto& details = call_data->arg2()->GetDict();
    ASSERT_EQ(details.size(), 3u);

    std::optional<int> key_code = details.FindInt("keyCode");
    ASSERT_NE(key_code, std::nullopt);
    ASSERT_EQ(*key_code, code);

    const std::string* key = details.FindString("key");
    ASSERT_NE(key, nullptr);
    ASSERT_EQ(*key, name);

    const std::string* device = details.FindString("device");
    ASSERT_NE(device, nullptr);
    ASSERT_EQ(*device, "unknown");
  }

  content::TestWebUI::CallData* FindCallForEvent(const std::string& event) {
    auto iter = std::find_if(
        web_ui()->call_data().cbegin(), web_ui()->call_data().cend(),
        [&event](const std::unique_ptr<content::TestWebUI::CallData>& data) {
          if (data->function_name() != "cr.webUIListenerCallback") {
            return false;
          }
          if (data->args().size() < 1) {
            return false;
          }
          if (!data->arg1()->is_string()) {
            return false;
          }
          return data->arg1()->GetString() == event;
        });

    if (iter != web_ui()->call_data().cend()) {
      return iter->get();
    }
    return nullptr;
  }

 private:
  std::unique_ptr<TestSwitchAccessHandler> handler_;
  content::TestWebUI web_ui_;
};

IN_PROC_BROWSER_TEST_F(SwitchAccessHandlerTest,
                       SwitchAssignmentReceivesKeyEvents) {
  // First test the case where JavaScript is not yet allowed.
  ASSERT_FALSE(handler()->IsJavascriptAllowed());
  base::RunLoop run_loop;
  handler()->set_on_listeners_added(run_loop.QuitClosure());
  SetPaneActive();
  // Confirm the listeners are added.
  run_loop.Run();

  ui::KeyEvent key_event(ui::EventType::kKeyPressed, ui::VKEY_SPACE,
                         ui::EF_NONE);
  handler()->OnKeyEvent(&key_event);

  // Verify the method was called correctly.
  AssertKeyPressData(ui::VKEY_SPACE, "Space");

  web_ui()->ClearTrackedCalls();
  SetPaneInactive();

  // Next test the case where JavaScript has already been allowed.
  ASSERT_TRUE(handler()->IsJavascriptAllowed());
  base::RunLoop run_loop_2;
  handler()->set_on_listeners_added(run_loop_2.QuitClosure());
  SetPaneActive();
  // Confirm the listeners are added.
  run_loop_2.Run();

  key_event =
      ui::KeyEvent(ui::EventType::kKeyPressed, ui::VKEY_TAB, ui::EF_NONE);
  handler()->OnKeyEvent(&key_event);

  // Verify the method was called correctly.
  AssertKeyPressData(ui::VKEY_TAB, "Tab");
}

}  // namespace ash::settings