// Copyright 2014 The Flutter 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:flutter_test/flutter_test.dart'; void main() { testWidgets('debugPrintGestureArenaDiagnostics', (WidgetTester tester) async { PointerEvent event; debugPrintGestureArenaDiagnostics = true; final DebugPrintCallback oldCallback = debugPrint; final log = []; debugPrint = (String? s, {int? wrapWidth}) { log.add(s ?? ''); }; final tap = TapGestureRecognizer() ..onTapDown = (TapDownDetails details) {} ..onTapUp = (TapUpDetails details) {} ..onTap = () {} ..onTapCancel = () {}; expect(log, isEmpty); event = const PointerDownEvent(pointer: 1, position: Offset(10.0, 10.0)); tap.addPointer(event as PointerDownEvent); expect(log, hasLength(2)); expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ ★ Opening new gesture arena.')); expect( log[1], equalsIgnoringHashCodes( 'Gesture arena 1 ❙ Adding: TapGestureRecognizer#00000(state: ready, button: 1)', ), ); log.clear(); GestureBinding.instance.gestureArena.close(1); expect(log, hasLength(1)); expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ Closing with 1 member.')); log.clear(); GestureBinding.instance.pointerRouter.route(event); expect(log, isEmpty); event = const PointerUpEvent(pointer: 1, position: Offset(12.0, 8.0)); GestureBinding.instance.pointerRouter.route(event); expect(log, isEmpty); GestureBinding.instance.gestureArena.sweep(1); expect(log, hasLength(2)); expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ Sweeping with 1 member.')); expect( log[1], equalsIgnoringHashCodes( 'Gesture arena 1 ❙ Winner: TapGestureRecognizer#00000(state: ready, finalPosition: Offset(12.0, 8.0), button: 1)', ), ); log.clear(); tap.dispose(); expect(log, isEmpty); debugPrintGestureArenaDiagnostics = false; debugPrint = oldCallback; }); testWidgets('debugPrintRecognizerCallbacksTrace', (WidgetTester tester) async { PointerEvent event; debugPrintRecognizerCallbacksTrace = true; final DebugPrintCallback oldCallback = debugPrint; final log = []; debugPrint = (String? s, {int? wrapWidth}) { log.add(s ?? ''); }; final tap = TapGestureRecognizer() ..onTapDown = (TapDownDetails details) {} ..onTapUp = (TapUpDetails details) {} ..onTap = () {} ..onTapCancel = () {}; expect(log, isEmpty); event = const PointerDownEvent(pointer: 1, position: Offset(10.0, 10.0)); tap.addPointer(event as PointerDownEvent); expect(log, isEmpty); GestureBinding.instance.gestureArena.close(1); expect(log, isEmpty); GestureBinding.instance.pointerRouter.route(event); expect(log, isEmpty); event = const PointerUpEvent(pointer: 1, position: Offset(12.0, 8.0)); GestureBinding.instance.pointerRouter.route(event); expect(log, isEmpty); GestureBinding.instance.gestureArena.sweep(1); expect(log, hasLength(3)); expect( log[0], equalsIgnoringHashCodes( 'TapGestureRecognizer#00000(state: ready, finalPosition: Offset(12.0, 8.0), button: 1) calling onTapDown callback.', ), ); expect( log[1], equalsIgnoringHashCodes( 'TapGestureRecognizer#00000(state: ready, won arena, finalPosition: Offset(12.0, 8.0), button: 1, sent tap down) calling onTapUp callback.', ), ); expect( log[2], equalsIgnoringHashCodes( 'TapGestureRecognizer#00000(state: ready, won arena, finalPosition: Offset(12.0, 8.0), button: 1, sent tap down) calling onTap callback.', ), ); log.clear(); tap.dispose(); expect(log, isEmpty); debugPrintRecognizerCallbacksTrace = false; debugPrint = oldCallback; }); testWidgets('debugPrintGestureArenaDiagnostics and debugPrintRecognizerCallbacksTrace', ( WidgetTester tester, ) async { PointerEvent event; debugPrintGestureArenaDiagnostics = true; debugPrintRecognizerCallbacksTrace = true; final DebugPrintCallback oldCallback = debugPrint; final log = []; debugPrint = (String? s, {int? wrapWidth}) { log.add(s ?? ''); }; final tap = TapGestureRecognizer() ..onTapDown = (TapDownDetails details) {} ..onTapUp = (TapUpDetails details) {} ..onTap = () {} ..onTapCancel = () {}; expect(log, isEmpty); event = const PointerDownEvent(pointer: 1, position: Offset(10.0, 10.0)); tap.addPointer(event as PointerDownEvent); expect(log, hasLength(2)); expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ ★ Opening new gesture arena.')); expect( log[1], equalsIgnoringHashCodes( 'Gesture arena 1 ❙ Adding: TapGestureRecognizer#00000(state: ready, button: 1)', ), ); log.clear(); GestureBinding.instance.gestureArena.close(1); expect(log, hasLength(1)); expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ Closing with 1 member.')); log.clear(); GestureBinding.instance.pointerRouter.route(event); expect(log, isEmpty); event = const PointerUpEvent(pointer: 1, position: Offset(12.0, 8.0)); GestureBinding.instance.pointerRouter.route(event); expect(log, isEmpty); GestureBinding.instance.gestureArena.sweep(1); expect(log, hasLength(5)); expect(log[0], equalsIgnoringHashCodes('Gesture arena 1 ❙ Sweeping with 1 member.')); expect( log[1], equalsIgnoringHashCodes( 'Gesture arena 1 ❙ Winner: TapGestureRecognizer#00000(state: ready, finalPosition: Offset(12.0, 8.0), button: 1)', ), ); expect( log[2], equalsIgnoringHashCodes( ' ❙ TapGestureRecognizer#00000(state: ready, finalPosition: Offset(12.0, 8.0), button: 1) calling onTapDown callback.', ), ); expect( log[3], equalsIgnoringHashCodes( ' ❙ TapGestureRecognizer#00000(state: ready, won arena, finalPosition: Offset(12.0, 8.0), button: 1, sent tap down) calling onTapUp callback.', ), ); expect( log[4], equalsIgnoringHashCodes( ' ❙ TapGestureRecognizer#00000(state: ready, won arena, finalPosition: Offset(12.0, 8.0), button: 1, sent tap down) calling onTap callback.', ), ); log.clear(); tap.dispose(); expect(log, isEmpty); debugPrintGestureArenaDiagnostics = false; debugPrintRecognizerCallbacksTrace = false; debugPrint = oldCallback; }); test('TapGestureRecognizer _sentTapDown toString', () { final tap = TapGestureRecognizer()..onTap = () {}; // Add a callback so that event can be added expect(tap.toString(), equalsIgnoringHashCodes('TapGestureRecognizer#00000(state: ready)')); const event = PointerDownEvent(pointer: 1, position: Offset(10.0, 10.0)); tap.addPointer(event); tap.didExceedDeadline(); expect( tap.toString(), equalsIgnoringHashCodes( 'TapGestureRecognizer#00000(state: possible, button: 1, sent tap down)', ), ); GestureBinding.instance.gestureArena.close(1); tap.dispose(); }); test('Gesture details debugFillProperties', () { final pairs = <(Diagnosticable, List)>[ ( DragDownDetails(), ['globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)'], ), ( DragStartDetails(), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'sourceTimeStamp: null', 'kind: null', ], ), ( DragUpdateDetails(globalPosition: Offset.zero), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'sourceTimeStamp: null', 'delta: Offset(0.0, 0.0)', 'primaryDelta: null', ], ), ( DragEndDetails(), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'velocity: Velocity(0.0, 0.0)', 'primaryVelocity: null', ], ), ( ForcePressDetails(globalPosition: Offset.zero, pressure: 1.0), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'pressure: 1.0', ], ), ( const LongPressDownDetails(), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'kind: null', ], ), ( const LongPressStartDetails(), ['globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)'], ), ( const LongPressMoveUpdateDetails(), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'offsetFromOrigin: Offset(0.0, 0.0)', 'localOffsetFromOrigin: Offset(0.0, 0.0)', ], ), ( const LongPressEndDetails(), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'velocity: Velocity(0.0, 0.0)', ], ), ( SerialTapDownDetails(kind: PointerDeviceKind.unknown), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'kind: unknown', 'buttons: 0', 'count: 1', ], ), (SerialTapCancelDetails(), ['count: 1']), ( SerialTapUpDetails(), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'kind: null', 'count: 1', ], ), ( ScaleStartDetails(), [ 'focalPoint: Offset(0.0, 0.0)', 'localFocalPoint: Offset(0.0, 0.0)', 'pointerCount: 0', 'sourceTimeStamp: null', ], ), ( ScaleUpdateDetails(), [ 'focalPointDelta: Offset(0.0, 0.0)', 'focalPoint: Offset(0.0, 0.0)', 'localFocalPoint: Offset(0.0, 0.0)', 'scale: 1.0', 'horizontalScale: 1.0', 'verticalScale: 1.0', 'rotation: 0.0', 'pointerCount: 0', 'sourceTimeStamp: null', ], ), ( ScaleEndDetails(), ['velocity: Velocity(0.0, 0.0)', 'scaleVelocity: 0.0', 'pointerCount: 0'], ), ( TapDownDetails(kind: PointerDeviceKind.unknown), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'kind: unknown', ], ), ( TapUpDetails(kind: PointerDeviceKind.unknown), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'kind: unknown', ], ), ( TapDragDownDetails( globalPosition: Offset.zero, localPosition: Offset.zero, consecutiveTapCount: 1, ), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'kind: null', 'consecutiveTapCount: 1', ], ), ( TapDragUpDetails( globalPosition: Offset.zero, localPosition: Offset.zero, kind: PointerDeviceKind.unknown, consecutiveTapCount: 1, ), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'kind: unknown', 'consecutiveTapCount: 1', ], ), ( TapDragStartDetails( globalPosition: Offset.zero, localPosition: Offset.zero, consecutiveTapCount: 1, ), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'sourceTimeStamp: null', 'kind: null', 'consecutiveTapCount: 1', ], ), ( TapDragUpdateDetails( globalPosition: Offset.zero, localPosition: Offset.zero, offsetFromOrigin: Offset.zero, localOffsetFromOrigin: Offset.zero, consecutiveTapCount: 1, ), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'sourceTimeStamp: null', 'delta: Offset(0.0, 0.0)', 'primaryDelta: null', 'kind: null', 'offsetFromOrigin: Offset(0.0, 0.0)', 'localOffsetFromOrigin: Offset(0.0, 0.0)', 'consecutiveTapCount: 1', ], ), ( TapDragEndDetails(consecutiveTapCount: 1), [ 'globalPosition: Offset(0.0, 0.0)', 'localPosition: Offset(0.0, 0.0)', 'velocity: Velocity(0.0, 0.0)', 'primaryVelocity: null', 'consecutiveTapCount: 1', ], ), ]; for (final (Diagnosticable detail, List expected) in pairs) { final builder = DiagnosticPropertiesBuilder(); // ignore: invalid_use_of_protected_member detail.debugFillProperties(builder); final List description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect(description, expected); } }); }