blob: 0d733707d48a2b1862a575ca026e559d266ad927 [file] [log] [blame]
// Copyright 2020 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.
// ignore_for_file: avoid_as, dead_code, null_check_always_fails
import 'dart:async';
import 'package:fidl/fidl.dart';
import 'package:fidl_fuchsia_ui_pointerinjector/fidl_async.dart';
import 'package:fidl_fuchsia_ui_views/fidl_async.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fuchsia_logger/logger.dart';
// ignore: implementation_imports
import 'package:fuchsia_scenic_flutter/src/pointer_injector.dart';
import 'package:mockito/mockito.dart';
import 'package:pedantic/pedantic.dart';
import 'package:zircon/zircon.dart';
void main() {
setupLogger();
test('PointerInjector register', () async {
final device = _mockDevice();
final registry = _mockRegistry();
final injector = PointerInjector(registry, device, onError: () {});
final hostViewRef = _mockViewRef();
final viewRef = _mockViewRef();
injector.register(hostViewRef: hostViewRef, viewRef: viewRef);
verify(registry.register(captureAny, any)).captured.single;
expect(injector.registered, isTrue);
});
test('PointerInjector dispatchEvent', () async {
MockDevice device = _mockDevice() as MockDevice;
final registry = _mockRegistry();
final injector = PointerInjector(registry, device, onError: () {});
when(device.inject(any)).thenAnswer((_) => Future<void>.value());
final pointer = PointerDownEvent(position: Offset(10, 10));
await injector.dispatchEvent(pointer: pointer);
final List<Event> events =
verify(device.inject(captureAny)).captured.single;
expect(events.length, 1);
final pointerEvent = events.first;
expect(pointerEvent.data!.pointerSample!.phase, EventPhase.add);
expect(pointerEvent.data!.pointerSample!.positionInViewport, [10.0, 10.0]);
});
test('PointerInjector dispatchEvent logs exception and onError', () async {
MockDevice device = _mockDevice() as MockDevice;
final registry = _mockRegistry();
bool error = false;
final injector =
PointerInjector(registry, device, onError: () => error = true);
when(device.inject(any)).thenAnswer((_) =>
Future<void>.error(FidlStateException('Proxy<Device> is closed.')));
// Check for errors being thrown or logs being generated.
bool logsError = false;
log.onRecord.listen((event) {
logsError = true;
});
final pointer = PointerDownEvent(position: Offset(10, 10));
await injector.dispatchEvent(
pointer: pointer,
);
expect(logsError, isTrue);
expect(error, isTrue);
});
test('PointerInjector dispatchEvent is correctly reentrant', () async {
MockDevice device = _mockDevice() as MockDevice;
final registry = _mockRegistry();
final injector = PointerInjector(registry, device, onError: () {});
// Control the wakeup time of each device.inject FIDL call, independently.
// There are three 'dispatchEvent' Dart calls, but two 'device.inject' FIDL
// calls (because flow control).
final Completer<void> injectCallFinished_1 = Completer<void>();
final Completer<void> injectCallFinished_2 = Completer<void>();
final answers = [injectCallFinished_1, injectCallFinished_2];
when(device.inject(any)).thenAnswer((_) => answers.removeAt(0).future);
// Make the Dart calls.
final down = PointerDownEvent(position: Offset(10, 10));
unawaited(injector.dispatchEvent(
pointer: down,
));
final move = PointerMoveEvent(position: Offset(15, 15));
unawaited(injector.dispatchEvent(
pointer: move,
));
final up = PointerUpEvent(position: Offset(20, 20));
unawaited(injector.dispatchEvent(
pointer: up,
));
// Allow the first 'dispatchEvent' call to continue draining the buffer.
injectCallFinished_1.complete(); // resume loop
// Allow the first 'dispatchEvent' call to exit the drain loop.
injectCallFinished_2.complete(); // resume loop
// Sequence the verification step to *after* the loop finishes completely.
await untilCalled(device.inject(any));
var verification = verify(device.inject(captureAny));
expect(verification.captured.length, 2); // two FIDL injections
expect(verification.captured[0].length, 1); // first batch, just the add
expect(
verification.captured[1].length, 2); // second batch, change and remove
final downInjected = verification.captured[0].single;
expect(downInjected.data!.pointerSample!.phase, EventPhase.add);
expect(downInjected.data!.pointerSample!.positionInViewport, [10.0, 10.0]);
final moveInjected = verification.captured[1][0];
expect(moveInjected.data!.pointerSample!.phase, EventPhase.change);
expect(moveInjected.data!.pointerSample!.positionInViewport, [15.0, 15.0]);
final upInjected = verification.captured[1][1];
expect(upInjected.data!.pointerSample!.phase, EventPhase.remove);
expect(upInjected.data!.pointerSample!.positionInViewport, [20.0, 20.0]);
});
}
ViewRef _mockViewRef() {
final viewRef = MockViewRef();
final eventPair = MockEventPair();
when(viewRef.reference).thenReturn(eventPair);
when(eventPair.duplicate(any)).thenReturn(eventPair);
when(eventPair.isValid).thenReturn(true);
return viewRef;
}
MockRegistry _mockRegistry() {
final registry = MockRegistry();
when(registry.register(any, any)).thenAnswer((_) => Future<void>.value());
return registry;
}
DeviceProxy _mockDevice() {
final device = MockDevice();
final ctrl = MockCtrl();
when(device.ctrl).thenReturn(ctrl);
return device;
}
class MockCtrl extends Mock implements AsyncProxyController<Device> {}
class MockDevice extends Mock implements DeviceProxy {
@override
Future<void> inject(List<Event>? events) =>
super.noSuchMethod(Invocation.method(#inject, [events]));
}
class MockRegistry extends Mock implements Registry {
@override
Future<void> register(Config? config, InterfaceRequest<Device>? injector) =>
super.noSuchMethod(Invocation.method(#register, [config, injector]));
}
class MockViewRef extends Mock implements ViewRef {
@override
int get hashCode => super.noSuchMethod(Invocation.method(#hashCode, []));
@override
bool operator ==(dynamic other) =>
super.noSuchMethod(Invocation.method(#==, [other]));
}
class MockEventPair extends Mock implements EventPair {
@override
EventPair duplicate(int? options) =>
super.noSuchMethod(Invocation.method(#duplicate, [options]));
}