blob: 3c0598828e99eb716bdf0bb44294aa7b70b43895 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fidl/fuchsia.accessibility.semantics/cpp/fidl.h>
#include <fidl/fuchsia.buildinfo/cpp/fidl.h>
#include <fidl/fuchsia.component.decl/cpp/fidl.h>
#include <fidl/fuchsia.component.decl/cpp/hlcpp_conversion.h>
#include <fidl/fuchsia.component/cpp/fidl.h>
#include <fidl/fuchsia.element/cpp/fidl.h>
#include <fidl/fuchsia.fonts/cpp/fidl.h>
#include <fidl/fuchsia.input.report/cpp/fidl.h>
#include <fidl/fuchsia.kernel/cpp/fidl.h>
#include <fidl/fuchsia.logger/cpp/fidl.h>
#include <fidl/fuchsia.memorypressure/cpp/fidl.h>
#include <fidl/fuchsia.metrics/cpp/fidl.h>
#include <fidl/fuchsia.net.interfaces/cpp/fidl.h>
#include <fidl/fuchsia.posix.socket/cpp/fidl.h>
#include <fidl/fuchsia.process/cpp/fidl.h>
#include <fidl/fuchsia.scheduler/cpp/fidl.h>
#include <fidl/fuchsia.session.scene/cpp/fidl.h>
#include <fidl/fuchsia.tracing.provider/cpp/fidl.h>
#include <fidl/fuchsia.ui.input/cpp/fidl.h>
#include <fidl/fuchsia.ui.test.input/cpp/fidl.h>
#include <fidl/fuchsia.vulkan.loader/cpp/fidl.h>
#include <fidl/fuchsia.web/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/component/outgoing/cpp/outgoing_directory.h>
#include <lib/fidl/cpp/channel.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <lib/sys/component/cpp/testing/realm_builder_types.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/status.h>
#include <zircon/types.h>
#include <zircon/utc.h>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <memory>
#include <queue>
#include <string>
#include <utility>
#include <vector>
#include <gtest/gtest.h>
#include <src/ui/testing/util/portable_ui_test.h>
namespace {
// Types imported for the realm_builder library.
using component_testing::ChildRef;
using component_testing::Config;
using component_testing::Directory;
using component_testing::LocalComponentImpl;
using component_testing::ParentRef;
using component_testing::Protocol;
using component_testing::Route;
using component_testing::VoidRef;
// Alias for Component child name as provided to Realm Builder.
using ChildName = std::string;
// Maximum pointer movement during a clickpad press for the gesture to
// be guaranteed to be interpreted as a click. For movement greater than
// this value, upper layers may, e.g., interpret the gesture as a drag.
//
// This value corresponds to the one used to instantiate the ClickDragHandler
// registered by Input Pipeline in Scene Manager.
constexpr int64_t kClickToDragThreshold = 16.0;
int ButtonsToInt(const std::vector<fuchsia_ui_test_input::MouseButton>& buttons) {
int result = 0;
for (const auto& button : buttons) {
result |= (0x1 >> static_cast<uint32_t>(button));
}
return result;
}
// Contains the current mouse input state.
//
// Used to be part of MouseInputListenerServer. The state is now externalized,
// because the new AddLocalChild API does not allow directly inspecting the
// state of the local child component itself. Instead, this state is shared
// between the test fixture and the local component below.
class MouseInputState {
public:
size_t SizeOfEvents() const { return events_.size(); }
fuchsia_ui_test_input::MouseInputListenerReportMouseInputRequest PopEvent() {
auto e = std::move(events_.front());
events_.pop();
return e;
}
const fuchsia_ui_test_input::MouseInputListenerReportMouseInputRequest& LastEvent() const {
return events_.back();
}
void ClearEvents() { events_ = {}; }
private:
friend class MouseInputListenerServer;
std::queue<fuchsia_ui_test_input::MouseInputListenerReportMouseInputRequest> events_;
};
// `MouseInputListener` is a local test protocol that our test apps use to let us know
// what position and button press state the mouse cursor has.
class MouseInputListenerServer : public fidl::Server<fuchsia_ui_test_input::MouseInputListener>,
public fidl::Server<fuchsia_ui_test_input::TestAppStatusListener>,
public LocalComponentImpl {
public:
explicit MouseInputListenerServer(async_dispatcher_t* dispatcher,
std::weak_ptr<MouseInputState> mouse_state,
std::weak_ptr<bool> ready_to_inject)
: dispatcher_(dispatcher),
mouse_state_(std::move(mouse_state)),
ready_to_inject_(std::move(ready_to_inject)) {}
void ReportMouseInput(ReportMouseInputRequest& request,
ReportMouseInputCompleter::Sync& completer) override {
if (auto s = mouse_state_.lock()) {
s->events_.push(std::move(request));
}
}
void ReportStatus(ReportStatusRequest& req, ReportStatusCompleter::Sync& completer) override {
if (req.status() == fuchsia_ui_test_input::TestAppStatus::kHandlersRegistered) {
if (auto ready = ready_to_inject_.lock()) {
*ready = true;
}
}
completer.Reply();
}
void handle_unknown_method(
fidl::UnknownMethodMetadata<fuchsia_ui_test_input::TestAppStatusListener> metadata,
fidl::UnknownMethodCompleter::Sync& completer) override {
FX_LOGS(WARNING) << "TestAppStatusListener Received an unknown method with ordinal "
<< metadata.method_ordinal;
}
// When the component framework requests for this component to start, this
// method will be invoked by the realm_builder library.
void OnStart() override {
outgoing()->AddProtocol<fuchsia_ui_test_input::MouseInputListener>(
mouse_bindings_.CreateHandler(this, dispatcher_, fidl::kIgnoreBindingClosure));
outgoing()->AddProtocol<fuchsia_ui_test_input::TestAppStatusListener>(
app_status_bindings_.CreateHandler(this, dispatcher_, fidl::kIgnoreBindingClosure));
}
private:
// Not owned.
async_dispatcher_t* dispatcher_ = nullptr;
fidl::ServerBindingGroup<fuchsia_ui_test_input::MouseInputListener> mouse_bindings_;
fidl::ServerBindingGroup<fuchsia_ui_test_input::TestAppStatusListener> app_status_bindings_;
std::weak_ptr<MouseInputState> mouse_state_;
std::weak_ptr<bool> ready_to_inject_;
};
constexpr auto kMouseInputListener = "mouse_input_listener";
struct Position {
double x = 0.0;
double y = 0.0;
};
class MouseInputBase : public ui_testing::PortableUITest {
protected:
MouseInputBase() : mouse_state_(std::make_shared<MouseInputState>()) {}
std::string GetTestUIStackUrl() override { return "#meta/test-ui-stack.cm"; }
void SetUp() override {
ui_testing::PortableUITest::SetUp();
// Register fake mouse device.
RegisterMouse();
}
void TearDown() override {
ui_testing::PortableUITest::TearDown();
// at the end of test, ensure event queue is empty.
ASSERT_EQ(mouse_state_->SizeOfEvents(), 0u);
}
// Helper method for checking the test.mouse.MouseInputListener response from the client app.
static void VerifyEvent(
fuchsia_ui_test_input::MouseInputListenerReportMouseInputRequest& pointer_data,
double expected_x, double expected_y,
const std::vector<fuchsia_ui_test_input::MouseButton>& expected_buttons,
const fuchsia_ui_test_input::MouseEventPhase expected_phase,
const std::string& component_name) {
FX_LOGS(INFO) << "Client received mouse change at (" << pointer_data.local_x().value() << ", "
<< pointer_data.local_y().value() << ") with buttons "
<< ButtonsToInt(pointer_data.buttons().value()) << ".";
FX_LOGS(INFO) << "Expected mouse change is at approximately (" << expected_x << ", "
<< expected_y << ") with buttons " << ButtonsToInt(expected_buttons) << ".";
// Allow for minor rounding differences in coordinates.
// Note: These approximations don't account for `PointerMotionDisplayScaleHandler`
// or `PointerMotionSensorScaleHandler`. We will need to do so in order to validate
// larger motion or different sized displays.
EXPECT_NEAR(pointer_data.local_x().value(), expected_x, 1);
EXPECT_NEAR(pointer_data.local_y().value(), expected_y, 1);
EXPECT_EQ(pointer_data.buttons().value(), expected_buttons);
EXPECT_EQ(pointer_data.phase().value(), expected_phase);
EXPECT_EQ(pointer_data.component_name().value(), component_name);
}
static void VerifyEventLocationOnTheRightOfExpectation(
fuchsia_ui_test_input::MouseInputListenerReportMouseInputRequest& pointer_data,
double expected_x_min, double expected_y,
const std::vector<fuchsia_ui_test_input::MouseButton>& expected_buttons,
const fuchsia_ui_test_input::MouseEventPhase expected_phase,
const std::string& component_name) {
FX_LOGS(INFO) << "Client received mouse change at (" << pointer_data.local_x().value() << ", "
<< pointer_data.local_y().value() << ") with buttons "
<< ButtonsToInt(pointer_data.buttons().value()) << ".";
FX_LOGS(INFO) << "Expected mouse change is at approximately (>" << expected_x_min << ", "
<< expected_y << ") with buttons " << ButtonsToInt(expected_buttons) << ".";
EXPECT_GT(pointer_data.local_x().value(), expected_x_min);
EXPECT_NEAR(pointer_data.local_y().value(), expected_y, 1);
EXPECT_EQ(pointer_data.buttons().value(), expected_buttons);
EXPECT_EQ(pointer_data.phase().value(), expected_phase);
EXPECT_EQ(pointer_data.component_name().value(), component_name);
}
void ExtendRealm() override {
// Key part of service setup: have this test component vend the
// |MouseInputListener| service in the constructed realm.
auto* d = dispatcher();
realm_builder().AddLocalChild(
kMouseInputListener, [d, mouse_state = mouse_state_, ready_to_inject = ready_to_inject_]() {
return std::make_unique<MouseInputListenerServer>(d, mouse_state, ready_to_inject);
});
}
std::shared_ptr<MouseInputState> mouse_state_;
std::shared_ptr<bool> ready_to_inject_ = std::make_shared<bool>(false);
// Use a DPR other than 1.0, so that logical and physical coordinate spaces
// are different.
float device_pixel_ratio() override { return 2.f; }
static constexpr auto kFontsProvider = "fonts_provider";
static constexpr auto kFontsProviderUrl = "#meta/font_provider_hermetic_for_test.cm";
};
class ChromiumInputTest : public MouseInputBase {
public:
void SetUp() override {
MouseInputBase::SetUp();
WaitForViewPresentation();
FX_LOGS(INFO) << "Wait for Chromium send out ready";
RunLoopUntil([this]() { return *(ready_to_inject_); });
}
protected:
std::vector<std::pair<ChildName, std::string>> GetEagerTestComponents() override {
return {
std::make_pair(kMouseInputChromium, kMouseInputChromiumUrl),
};
}
std::vector<std::pair<ChildName, std::string>> GetTestComponents() override {
return {
std::make_pair(kBuildInfoProvider, kBuildInfoProviderUrl),
std::make_pair(kFontsProvider, kFontsProviderUrl),
std::make_pair(kMemoryPressureSignaler, kMemoryPressureSignalerUrl),
std::make_pair(kNetstack, kNetstackUrl),
std::make_pair(kFakeCobalt, kFakeCobaltUrl),
std::make_pair(kWebContextProvider, kWebContextProviderUrl),
};
}
std::vector<Route> GetTestRoutes() override {
return GetChromiumRoutes(ChildRef{kMouseInputChromium});
}
// Routes needed to setup Chromium client.
static std::vector<Route> GetChromiumRoutes(ChildRef target) {
return {
{.capabilities =
{
Protocol{fidl::DiscoverableProtocolName<
fuchsia_accessibility_semantics::SemanticsManager>},
Protocol{fidl::DiscoverableProtocolName<fuchsia_element::GraphicalPresenter>},
Protocol{fidl::DiscoverableProtocolName<fuchsia_ui_composition::Allocator>},
Protocol{fidl::DiscoverableProtocolName<fuchsia_ui_composition::Flatland>},
},
.source = kTestUIStackRef,
.targets = {target}},
{.capabilities =
{
Protocol{fidl::DiscoverableProtocolName<fuchsia_kernel::VmexResource>},
Protocol{fidl::DiscoverableProtocolName<fuchsia_process::Launcher>},
Protocol{fidl::DiscoverableProtocolName<fuchsia_vulkan_loader::Loader>},
},
.source = ParentRef(),
.targets = {target}},
{
.capabilities =
{
Protocol{fidl::DiscoverableProtocolName<fuchsia_logger::LogSink>},
},
.source = ParentRef(),
.targets =
{
target,
ChildRef{kFontsProvider},
ChildRef{kMemoryPressureSignaler},
ChildRef{kBuildInfoProvider},
ChildRef{kWebContextProvider},
ChildRef{kFakeCobalt},
// Not including kNetstack here, since it emits spurious
// FATAL errors.
},
},
{.capabilities =
{
Protocol{
fidl::DiscoverableProtocolName<fuchsia_ui_test_input::MouseInputListener>},
Protocol{
fidl::DiscoverableProtocolName<fuchsia_ui_test_input::TestAppStatusListener>},
},
.source = ChildRef{kMouseInputListener},
.targets = {target}},
{.capabilities = {Protocol{fidl::DiscoverableProtocolName<fuchsia_fonts::Provider>}},
.source = ChildRef{kFontsProvider},
.targets = {target}},
{.capabilities =
{
Protocol{fidl::DiscoverableProtocolName<fuchsia_tracing_provider::Registry>},
},
.source = ParentRef(),
.targets = {target, ChildRef{kFontsProvider}}},
{.capabilities = {Protocol{
fidl::DiscoverableProtocolName<fuchsia_memorypressure::Provider>}},
.source = ChildRef{kMemoryPressureSignaler},
.targets = {target}},
{.capabilities = {Protocol{fidl::DiscoverableProtocolName<fuchsia_net_interfaces::State>}},
.source = ChildRef{kNetstack},
.targets = {target}},
{.capabilities = {Protocol{fidl::DiscoverableProtocolName<fuchsia_web::ContextProvider>}},
.source = ChildRef{kWebContextProvider},
.targets = {target}},
{.capabilities = {Protocol{
fidl::DiscoverableProtocolName<fuchsia_metrics::MetricEventLoggerFactory>}},
.source = ChildRef{kFakeCobalt},
.targets = {ChildRef{kMemoryPressureSignaler}}},
{.capabilities = {Protocol{fidl::DiscoverableProtocolName<fuchsia_sysmem::Allocator>},
Protocol{fidl::DiscoverableProtocolName<fuchsia_sysmem2::Allocator>}},
.source = ParentRef(),
.targets = {ChildRef{kMemoryPressureSignaler}, target}},
{.capabilities = {Protocol{fidl::DiscoverableProtocolName<fuchsia_scheduler::RoleManager>}},
.source = ParentRef(),
.targets = {ChildRef{kMemoryPressureSignaler}}},
{.capabilities = {Protocol{
fidl::DiscoverableProtocolName<fuchsia_kernel::RootJobForInspect>}},
.source = ParentRef(),
.targets = {ChildRef{kMemoryPressureSignaler}}},
{.capabilities = {Protocol{fidl::DiscoverableProtocolName<fuchsia_kernel::Stats>}},
.source = ParentRef(),
.targets = {ChildRef{kMemoryPressureSignaler}}},
{.capabilities = {Protocol{
fidl::DiscoverableProtocolName<fuchsia_tracing_provider::Registry>}},
.source = ParentRef(),
.targets = {ChildRef{kMemoryPressureSignaler}}},
{.capabilities = {Protocol{fidl::DiscoverableProtocolName<fuchsia_posix_socket::Provider>}},
.source = ChildRef{kNetstack},
.targets = {target}},
{.capabilities = {Protocol{fidl::DiscoverableProtocolName<fuchsia_buildinfo::Provider>}},
.source = ChildRef{kBuildInfoProvider},
.targets = {ChildRef{kWebContextProvider}, target}},
{.capabilities =
{
Directory{
.name = "root-ssl-certificates",
.type = fidl::NaturalToHLCPP(fuchsia_component_decl::DependencyType::kStrong),
},
Directory{
.name = "tzdata-icu",
.type = fidl::NaturalToHLCPP(fuchsia_component_decl::DependencyType::kStrong),
},
},
.source = ParentRef(),
.targets = {ChildRef{kWebContextProvider}}},
};
}
// TODO(https://fxbug.dev/42136267): EnsureMouseIsReadyAndGetPosition will send a mouse click
// (down and up) and wait for response to ensure the mouse is ready to use. We will retry a
// mouse click if we can not get the mouseup response in small timeout. This function returns
// the cursor position in WebEngine coordinate system.
Position EnsureMouseIsReadyAndGetPosition() {
for (int retry = 0; retry < kMaxRetry; retry++) {
// Mouse down and up.
SimulateMouseEvent(/* pressed_buttons = */ {fuchsia_ui_test_input::MouseButton::kFirst},
/* movement_x = */ 0, /* movement_y = */ 0);
SimulateMouseEvent(/* pressed_buttons = */ {}, /* movement_x = */ 0, /* movement_y = */ 0);
auto wait_until_last_event_phase_or_timeout =
[this](fuchsia_ui_test_input::MouseEventPhase event_phase) {
return RunLoopWithTimeoutOrUntil(
[this, event_phase] {
return mouse_state_->SizeOfEvents() > 0 &&
mouse_state_->LastEvent().phase() == event_phase;
},
kFirstEventRetryInterval);
};
bool got_mouse_up =
wait_until_last_event_phase_or_timeout(fuchsia_ui_test_input::MouseEventPhase::kUp);
if (got_mouse_up) {
// There is an issue we found in retry that the mouse up we got may
// come from previous retry loop, then EnsureMouseIsReadyAndGetPosition
// exit and the down-up event caught by test body, and break the test
// expectation. Here we inject 1 more wheel event, because wheel event
// only injected once, wheel we receive the wheel event, we know all
// events from EnsureMouseIsReadyAndGetPosition are processed.
SimulateMouseScroll(/* pressed_buttons = */ {}, /* scroll_x = */ 0, /* scroll_y = */ 1);
wait_until_last_event_phase_or_timeout(fuchsia_ui_test_input::MouseEventPhase::kWheel);
Position p;
p.x = mouse_state_->LastEvent().local_x().value();
p.y = mouse_state_->LastEvent().local_y().value();
mouse_state_->ClearEvents();
return p;
}
}
FX_LOGS(FATAL) << "Can not get mouse click in max retries " << kMaxRetry;
return Position{};
}
static constexpr auto kMouseInputChromium = "mouse-input-chromium";
static constexpr auto kMouseInputChromiumUrl = "#meta/mouse-input-chromium.cm";
static constexpr auto kWebContextProvider = "web_context_provider";
static constexpr auto kWebContextProviderUrl =
"fuchsia-pkg://fuchsia.com/web_engine#meta/context_provider.cm";
static constexpr auto kMemoryPressureSignaler = "memory_pressure_signaler";
static constexpr auto kMemoryPressureSignalerUrl = "#meta/memory_pressure_signaler.cm";
static constexpr auto kNetstack = "netstack";
static constexpr auto kNetstackUrl = "#meta/netstack.cm";
static constexpr auto kBuildInfoProvider = "build_info_provider";
static constexpr auto kBuildInfoProviderUrl = "#meta/fake_build_info.cm";
static constexpr auto kFakeCobalt = "cobalt";
static constexpr auto kFakeCobaltUrl = "#meta/fake_cobalt.cm";
// The first event to WebEngine may lost, see EnsureMouseIsReadyAndGetPosition. Retry to ensure
// WebEngine is ready to process events.
static constexpr auto kFirstEventRetryInterval = zx::sec(1);
// To avoid retry to timeout, limit 10 retries, if still not ready, fail it with meaningful
// error.
static const int kMaxRetry = 10;
};
TEST_F(ChromiumInputTest, ChromiumMouseMove) {
auto initial_position = EnsureMouseIsReadyAndGetPosition();
double initial_x = initial_position.x;
double initial_y = initial_position.y;
SimulateMouseEvent(/* pressed_buttons = */ {},
/* movement_x = */ 5, /* movement_y = */ 0);
RunLoopUntil([this] { return mouse_state_->SizeOfEvents() == 1; });
auto event_move = mouse_state_->PopEvent();
VerifyEventLocationOnTheRightOfExpectation(
event_move,
/*expected_x_min=*/initial_x,
/*expected_y=*/initial_y,
/*expected_buttons=*/{},
/*expected_phase=*/fuchsia_ui_test_input::MouseEventPhase::kMove,
/*component_name=*/"mouse-input-chromium");
}
TEST_F(ChromiumInputTest, DISABLED_ChromiumMouseDownMoveUp) {
auto initial_position = EnsureMouseIsReadyAndGetPosition();
double initial_x = initial_position.x;
double initial_y = initial_position.y;
SimulateMouseEvent(/* pressed_buttons = */ {fuchsia_ui_test_input::MouseButton::kFirst},
/* movement_x = */ 0, /* movement_y = */ 0);
SimulateMouseEvent(/* pressed_buttons = */ {fuchsia_ui_test_input::MouseButton::kFirst},
/* movement_x = */ kClickToDragThreshold, /* movement_y = */ 0);
SimulateMouseEvent(/* pressed_buttons = */ {}, /* movement_x = */ 0, /* movement_y = */ 0);
RunLoopUntil([this] { return mouse_state_->SizeOfEvents() == 3; });
auto event_down = mouse_state_->PopEvent();
auto event_move = mouse_state_->PopEvent();
auto event_up = mouse_state_->PopEvent();
VerifyEvent(event_down,
/*expected_x=*/initial_x,
/*expected_y=*/initial_y,
/*expected_buttons=*/{fuchsia_ui_test_input::MouseButton::kFirst},
/*expected_phase=*/fuchsia_ui_test_input::MouseEventPhase::kDown,
/*component_name=*/"mouse-input-chromium");
VerifyEventLocationOnTheRightOfExpectation(
event_move,
/*expected_x_min=*/initial_x,
/*expected_y=*/initial_y,
/*expected_buttons=*/{fuchsia_ui_test_input::MouseButton::kFirst},
/*expected_phase=*/fuchsia_ui_test_input::MouseEventPhase::kMove,
/*component_name=*/"mouse-input-chromium");
VerifyEvent(event_up,
/*expected_x=*/event_move.local_x().value(),
/*expected_y=*/initial_y,
/*expected_buttons=*/{},
/*expected_phase=*/fuchsia_ui_test_input::MouseEventPhase::kUp,
/*component_name=*/"mouse-input-chromium");
}
TEST_F(ChromiumInputTest, DISABLED_ChromiumMouseWheel) {
auto initial_position = EnsureMouseIsReadyAndGetPosition();
double initial_x = initial_position.x;
double initial_y = initial_position.y;
SimulateMouseScroll(/* pressed_buttons = */ {}, /* scroll_x = */ 1, /* scroll_y = */ 0);
RunLoopUntil([this] { return mouse_state_->SizeOfEvents() == 1; });
auto event_wheel_h = mouse_state_->PopEvent();
VerifyEvent(event_wheel_h,
/*expected_x=*/initial_x,
/*expected_y=*/initial_y,
/*expected_buttons=*/{},
/*expected_phase=*/fuchsia_ui_test_input::MouseEventPhase::kWheel,
/*component_name=*/"mouse-input-chromium");
// Chromium will scale the count of ticks to pixel.
// Positive delta in Fuchsia means scroll left, and scroll left in JS is negative delta.
EXPECT_LT(event_wheel_h.wheel_x_physical_pixel(), 0);
EXPECT_EQ(event_wheel_h.wheel_y_physical_pixel(), 0);
SimulateMouseScroll(/* pressed_buttons = */ {}, /* scroll_x = */ 0, /* scroll_y = */ 1);
RunLoopUntil([this] { return mouse_state_->SizeOfEvents() == 1; });
auto event_wheel_v = mouse_state_->PopEvent();
VerifyEvent(event_wheel_v,
/*expected_x=*/initial_x,
/*expected_y=*/initial_y,
/*expected_buttons=*/{},
/*expected_phase=*/fuchsia_ui_test_input::MouseEventPhase::kWheel,
/*component_name=*/"mouse-input-chromium");
// Chromium will scale the count of ticks to pixel.
// Positive delta in Fuchsia means scroll up, and scroll up in JS is negative delta.
EXPECT_LT(event_wheel_v.wheel_y_physical_pixel(), 0);
EXPECT_EQ(event_wheel_v.wheel_x_physical_pixel(), 0);
}
} // namespace