// Copyright 2015 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:test/test.dart'; import 'gesture_tester.dart'; class TestGestureArenaMember extends GestureArenaMember { @override void acceptGesture(int key) {} @override void rejectGesture(int key) {} } void main() { setUp(ensureGestureBinding); // Down/up pair 1: normal tap sequence const PointerDownEvent down1 = const PointerDownEvent( pointer: 1, position: const Point(10.0, 10.0) ); const PointerUpEvent up1 = const PointerUpEvent( pointer: 1, position: const Point(11.0, 9.0) ); // Down/up pair 2: normal tap sequence far away from pair 1 const PointerDownEvent down2 = const PointerDownEvent( pointer: 2, position: const Point(30.0, 30.0) ); const PointerUpEvent up2 = const PointerUpEvent( pointer: 2, position: const Point(31.0, 29.0) ); // Down/move/up sequence 3: intervening motion const PointerDownEvent down3 = const PointerDownEvent( pointer: 3, position: const Point(10.0, 10.0) ); const PointerMoveEvent move3 = const PointerMoveEvent( pointer: 3, position: const Point(25.0, 25.0) ); const PointerUpEvent up3 = const PointerUpEvent( pointer: 3, position: const Point(25.0, 25.0) ); testGesture('Should recognize tap', (GestureTester tester) { TapGestureRecognizer tap = new TapGestureRecognizer(); bool tapRecognized = false; tap.onTap = () { tapRecognized = true; }; tap.addPointer(down1); tester.closeArena(1); expect(tapRecognized, isFalse); tester.route(down1); expect(tapRecognized, isFalse); tester.route(up1); expect(tapRecognized, isTrue); GestureBinding.instance.gestureArena.sweep(1); expect(tapRecognized, isTrue); tap.dispose(); }); testGesture('No duplicate tap events', (GestureTester tester) { TapGestureRecognizer tap = new TapGestureRecognizer(); int tapsRecognized = 0; tap.onTap = () { tapsRecognized++; }; tap.addPointer(down1); tester.closeArena(1); expect(tapsRecognized, 0); tester.route(down1); expect(tapsRecognized, 0); tester.route(up1); expect(tapsRecognized, 1); GestureBinding.instance.gestureArena.sweep(1); expect(tapsRecognized, 1); tap.addPointer(down1); tester.closeArena(1); expect(tapsRecognized, 1); tester.route(down1); expect(tapsRecognized, 1); tester.route(up1); expect(tapsRecognized, 2); GestureBinding.instance.gestureArena.sweep(1); expect(tapsRecognized, 2); tap.dispose(); }); testGesture('Should not recognize two overlapping taps', (GestureTester tester) { TapGestureRecognizer tap = new TapGestureRecognizer(); int tapsRecognized = 0; tap.onTap = () { tapsRecognized++; }; tap.addPointer(down1); tester.closeArena(1); expect(tapsRecognized, 0); tester.route(down1); expect(tapsRecognized, 0); tap.addPointer(down2); tester.closeArena(2); expect(tapsRecognized, 0); tester.route(down1); expect(tapsRecognized, 0); tester.route(up1); expect(tapsRecognized, 1); GestureBinding.instance.gestureArena.sweep(1); expect(tapsRecognized, 1); tester.route(up2); expect(tapsRecognized, 1); GestureBinding.instance.gestureArena.sweep(2); expect(tapsRecognized, 1); tap.dispose(); }); testGesture('Distance cancels tap', (GestureTester tester) { TapGestureRecognizer tap = new TapGestureRecognizer(); bool tapRecognized = false; tap.onTap = () { tapRecognized = true; }; bool tapCanceled = false; tap.onTapCancel = () { tapCanceled = true; }; tap.addPointer(down3); tester.closeArena(3); expect(tapRecognized, isFalse); expect(tapCanceled, isFalse); tester.route(down3); expect(tapRecognized, isFalse); expect(tapCanceled, isFalse); tester.route(move3); expect(tapRecognized, isFalse); expect(tapCanceled, isTrue); tester.route(up3); expect(tapRecognized, isFalse); expect(tapCanceled, isTrue); GestureBinding.instance.gestureArena.sweep(3); expect(tapRecognized, isFalse); expect(tapCanceled, isTrue); tap.dispose(); }); testGesture('Timeout does not cancel tap', (GestureTester tester) { TapGestureRecognizer tap = new TapGestureRecognizer(); bool tapRecognized = false; tap.onTap = () { tapRecognized = true; }; tap.addPointer(down1); tester.closeArena(1); expect(tapRecognized, isFalse); tester.route(down1); expect(tapRecognized, isFalse); tester.async.elapse(const Duration(milliseconds: 500)); expect(tapRecognized, isFalse); tester.route(up1); expect(tapRecognized, isTrue); GestureBinding.instance.gestureArena.sweep(1); expect(tapRecognized, isTrue); tap.dispose(); }); testGesture('Should yield to other arena members', (GestureTester tester) { TapGestureRecognizer tap = new TapGestureRecognizer(); bool tapRecognized = false; tap.onTap = () { tapRecognized = true; }; tap.addPointer(down1); TestGestureArenaMember member = new TestGestureArenaMember(); GestureArenaEntry entry = GestureBinding.instance.gestureArena.add(1, member); GestureBinding.instance.gestureArena.hold(1); tester.closeArena(1); expect(tapRecognized, isFalse); tester.route(down1); expect(tapRecognized, isFalse); tester.route(up1); expect(tapRecognized, isFalse); GestureBinding.instance.gestureArena.sweep(1); expect(tapRecognized, isFalse); entry.resolve(GestureDisposition.accepted); expect(tapRecognized, isFalse); tap.dispose(); }); testGesture('Should trigger on release of held arena', (GestureTester tester) { TapGestureRecognizer tap = new TapGestureRecognizer(); bool tapRecognized = false; tap.onTap = () { tapRecognized = true; }; tap.addPointer(down1); TestGestureArenaMember member = new TestGestureArenaMember(); GestureArenaEntry entry = GestureBinding.instance.gestureArena.add(1, member); GestureBinding.instance.gestureArena.hold(1); tester.closeArena(1); expect(tapRecognized, isFalse); tester.route(down1); expect(tapRecognized, isFalse); tester.route(up1); expect(tapRecognized, isFalse); GestureBinding.instance.gestureArena.sweep(1); expect(tapRecognized, isFalse); entry.resolve(GestureDisposition.rejected); tester.async.flushMicrotasks(); expect(tapRecognized, isTrue); tap.dispose(); }); testGesture('Should log exceptions from callbacks', (GestureTester tester) { TapGestureRecognizer tap = new TapGestureRecognizer(); tap.onTap = () { throw new Exception(test); }; FlutterExceptionHandler previousErrorHandler = FlutterError.onError; bool gotError = false; FlutterError.onError = (FlutterErrorDetails details) { gotError = true; }; tap.addPointer(down1); tester.closeArena(1); tester.route(down1); expect(gotError, isFalse); tester.route(up1); expect(gotError, isTrue); FlutterError.onError = previousErrorHandler; tap.dispose(); }); }