108using component_testing::ChildRef;
109using component_testing::ConfigValue;
110using component_testing::DirectoryContents;
111using component_testing::LocalComponentImpl;
112using component_testing::ParentRef;
113using component_testing::Protocol;
114using component_testing::Realm;
115using component_testing::RealmRoot;
116using component_testing::Route;
120using RealmBuilder = component_testing::RealmBuilder;
124constexpr zx::duration kTimeout = zx::min(1);
126constexpr auto kTestUIStackUrl =
127 "fuchsia-pkg://fuchsia.com/flatland-scene-manager-test-ui-stack#meta/"
130constexpr auto kMockTouchInputListener =
"touch_input_listener";
131constexpr auto kMockTouchInputListenerRef = ChildRef{kMockTouchInputListener};
133constexpr auto kTouchInputView =
"touch-input-view";
134constexpr auto kTouchInputViewRef = ChildRef{kTouchInputView};
135constexpr auto kTouchInputViewUrl =
"touch-input-view#meta/touch-input-view.cm";
136constexpr auto kEmbeddingFlutterView =
"embedding-flutter-view";
137constexpr auto kEmbeddingFlutterViewRef = ChildRef{kEmbeddingFlutterView};
138constexpr auto kEmbeddingFlutterViewUrl =
139 "embedding-flutter-view#meta/embedding-flutter-view.cm";
141bool CompareDouble(
double f0,
double f1,
double epsilon) {
142 return std::abs(f0 - f1) <= epsilon;
154class TouchInputListenerServer
155 :
public fuchsia::ui::test::input::TouchInputListener,
156 public LocalComponentImpl {
158 explicit TouchInputListenerServer(async_dispatcher_t* dispatcher)
159 : dispatcher_(dispatcher) {}
162 void ReportTouchInput(
163 fuchsia::ui::test::input::TouchInputListenerReportTouchInputRequest
165 FML_LOG(INFO) <<
"Received ReportTouchInput event";
166 events_received_.push_back(std::move(request));
172 void OnStart()
override {
173 FML_LOG(INFO) <<
"Starting TouchInputListenerServer";
176 ASSERT_EQ(ZX_OK, outgoing()->AddPublicService(
177 fidl::InterfaceRequestHandler<
178 fuchsia::ui::test::input::TouchInputListener>(
179 [
this](
auto request) {
180 bindings_.AddBinding(
this, std::move(request),
186 fuchsia::ui::test::input::TouchInputListenerReportTouchInputRequest>&
188 return events_received_;
192 async_dispatcher_t* dispatcher_ =
nullptr;
193 fidl::BindingSet<fuchsia::ui::test::input::TouchInputListener> bindings_;
195 fuchsia::ui::test::input::TouchInputListenerReportTouchInputRequest>
199class FlutterTapTestBase :
public PortableUITest,
public ::testing::Test {
201 ~FlutterTapTestBase()
override {
202 FML_CHECK(touch_injection_request_count() > 0)
203 <<
"Injection expected but didn't happen.";
206 void SetUp()
override {
207 PortableUITest::SetUp();
210 async::PostDelayedTask(
214 <<
"\n\n>> Test did not complete in time, terminating. <<\n\n";
221 <<
"Waiting for display info from fuchsia.ui.display.singleton.Info";
222 std::optional<bool> display_metrics_obtained;
223 fuchsia::ui::display::singleton::InfoPtr display_info =
226 .Connect<fuchsia::ui::display::singleton::Info>();
227 display_info->GetMetrics([
this, &display_metrics_obtained](
auto info) {
228 display_width_ = info.extent_in_px().width;
229 display_height_ = info.extent_in_px().height;
230 display_metrics_obtained =
true;
232 RunLoopUntil([&display_metrics_obtained] {
233 return display_metrics_obtained.has_value();
237 FML_LOG(INFO) <<
"Registering input injection device";
238 RegisterTouchScreen();
241 bool LastEventReceivedMatches(
float expected_x,
243 std::string component_name) {
244 const auto& events_received =
247 if (events_received.empty()) {
251 const auto& last_event = events_received.back();
253 auto pixel_scale = last_event.has_device_pixel_ratio()
254 ? last_event.device_pixel_ratio()
257 auto actual_x = pixel_scale * last_event.local_x();
258 auto actual_y = pixel_scale * last_event.local_y();
259 auto actual_component = last_event.component_name();
261 bool last_event_matches =
262 CompareDouble(actual_x, expected_x, pixel_scale) &&
263 CompareDouble(actual_y, expected_y, pixel_scale) &&
264 last_event.component_name() == component_name;
266 if (last_event_matches) {
267 FML_LOG(INFO) <<
"Received event for component " << component_name
268 <<
" at (" << expected_x <<
", " << expected_y <<
")";
270 FML_LOG(WARNING) <<
"Expecting event for component " << component_name
271 <<
" at (" << expected_x <<
", " << expected_y <<
"). "
272 <<
"Instead received event for component "
273 << actual_component <<
" at (" << actual_x <<
", "
274 << actual_y <<
"), accounting for pixel scale of "
278 return last_event_matches;
282 uint32_t display_width()
const {
return display_width_; }
283 uint32_t display_height()
const {
return display_height_; }
285 std::string GetTestUIStackUrl()
override {
return kTestUIStackUrl; };
290class FlutterTapTest :
public FlutterTapTestBase {
292 void ExtendRealm()
override {
293 FML_LOG(INFO) <<
"Extending realm";
296 auto touch_input_listener_server =
297 std::make_unique<TouchInputListenerServer>(dispatcher());
299 realm_builder()->AddLocalChild(
300 kMockTouchInputListener, [touch_input_listener_server = std::move(
301 touch_input_listener_server)]()
mutable {
302 return std::move(touch_input_listener_server);
306 realm_builder()->AddChild(kTouchInputView, kTouchInputViewUrl,
307 component_testing::ChildOptions{
308 .environment = kFlutterRunnerEnvironment,
312 realm_builder()->AddRoute(
313 Route{.capabilities = {Protocol{
314 fuchsia::ui::test::input::TouchInputListener::Name_}},
315 .source = kMockTouchInputListenerRef,
316 .targets = {kFlutterJitRunnerRef, kTouchInputViewRef}});
318 realm_builder()->AddRoute(
319 Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}},
320 .source = kTouchInputViewRef,
321 .targets = {ParentRef()}});
325class FlutterEmbedTapTest :
public FlutterTapTestBase {
327 void SetUp()
override {
328 PortableUITest::SetUp(
false);
331 async::PostDelayedTask(
335 <<
"\n\n>> Test did not complete in time, terminating. <<\n\n";
340 void LaunchClientWithEmbeddedView() {
346 <<
"Waiting for display info from fuchsia.ui.display.singleton.Info";
347 std::optional<bool> display_metrics_obtained;
348 fuchsia::ui::display::singleton::InfoPtr display_info =
351 .Connect<fuchsia::ui::display::singleton::Info>();
352 display_info->GetMetrics([
this, &display_metrics_obtained](
auto info) {
353 display_width_ = info.extent_in_px().width;
354 display_height_ = info.extent_in_px().height;
355 display_metrics_obtained =
true;
357 RunLoopUntil([&display_metrics_obtained] {
358 return display_metrics_obtained.has_value();
362 FML_LOG(INFO) <<
"Registering input injection device";
363 RegisterTouchScreen();
365 PortableUITest::LaunchClientWithEmbeddedView();
375 void AddComponentArgument(std::string component_arg) {
376 auto config_directory_contents = DirectoryContents();
377 config_directory_contents.AddFile(
"args.csv", component_arg);
378 realm_builder()->RouteReadOnlyDirectory(
379 "config-data", {kEmbeddingFlutterViewRef},
380 std::move(config_directory_contents));
384 void ExtendRealm()
override {
385 FML_LOG(INFO) <<
"Extending realm";
388 auto touch_input_listener_server =
389 std::make_unique<TouchInputListenerServer>(dispatcher());
391 realm_builder()->AddLocalChild(
392 kMockTouchInputListener, [touch_input_listener_server = std::move(
393 touch_input_listener_server)]()
mutable {
394 return std::move(touch_input_listener_server);
398 realm_builder()->AddChild(kTouchInputView, kTouchInputViewUrl,
399 component_testing::ChildOptions{
400 .environment = kFlutterRunnerEnvironment,
404 realm_builder()->AddChild(kEmbeddingFlutterView, kEmbeddingFlutterViewUrl,
405 component_testing::ChildOptions{
406 .environment = kFlutterRunnerEnvironment,
410 realm_builder()->AddRoute(
411 Route{.capabilities = {Protocol{
412 fuchsia::ui::test::input::TouchInputListener::Name_}},
413 .source = kMockTouchInputListenerRef,
414 .targets = {kFlutterJitRunnerRef, kTouchInputViewRef,
415 kEmbeddingFlutterViewRef}});
417 realm_builder()->AddRoute(
418 Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}},
419 .source = kEmbeddingFlutterViewRef,
420 .targets = {ParentRef()}});
421 realm_builder()->AddRoute(
422 Route{.capabilities = {Protocol{fuchsia::ui::app::ViewProvider::Name_}},
423 .source = kTouchInputViewRef,
424 .targets = {kEmbeddingFlutterViewRef}});
428TEST_F(FlutterTapTest, FlutterTap) {
430 FML_LOG(INFO) <<
"Initializing scene";
432 FML_LOG(INFO) <<
"Client launched";
438 InjectTap(-500, -500);
441 RunLoopUntil([
this] {
442 return LastEventReceivedMatches(
443 static_cast<float>(display_width() / 4.0f),
444 static_cast<float>(display_height() / 4.0f),
449 ASSERT_EQ(touch_injection_request_count(), 1);
452TEST_F(FlutterEmbedTapTest, FlutterEmbedTap) {
454 FML_LOG(INFO) <<
"Initializing scene";
455 LaunchClientWithEmbeddedView();
456 FML_LOG(INFO) <<
"Client launched";
462 RunLoopUntil([
this] {
463 return LastEventReceivedMatches(
464 static_cast<float>(display_width() / 8.0f),
465 static_cast<float>(display_height() / 8.0f),
474 RunLoopUntil([
this] {
475 return LastEventReceivedMatches(
476 static_cast<float>(display_width() / (4.0f / 3.0f)),
477 static_cast<float>(display_height() / (4.0f / 3.0f)),
478 "embedding-flutter-view");
483 ASSERT_EQ(touch_injection_request_count(), 2);
486TEST_F(FlutterEmbedTapTest, FlutterEmbedOverlayEnabled) {
487 FML_LOG(INFO) <<
"Initializing scene";
488 AddComponentArgument(
"--showOverlay");
489 LaunchClientWithEmbeddedView();
490 FML_LOG(INFO) <<
"Client launched";
497 RunLoopUntil([
this] {
498 return LastEventReceivedMatches(
499 static_cast<float>(display_width() / 2.0f),
500 static_cast<float>(display_height() / 2.0f),
501 "embedding-flutter-view");
510 RunLoopUntil([
this] {
511 return LastEventReceivedMatches(
512 static_cast<float>(display_width() / 8.0f),
513 static_cast<float>(display_height() / 8.0f),
519 ASSERT_EQ(touch_injection_request_count(), 2);