// 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 'dart:ui'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; void main() { testWidgetsWithLeakTracking('TapRegionSurface detects outside taps', (WidgetTester tester) async { final Set<String> tappedOutside = <String>{}; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Column( children: <Widget>[ const Text('Outside Surface'), TapRegionSurface( child: Row( children: <Widget>[ const Text('Outside'), TapRegion( onTapOutside: (PointerEvent event) { tappedOutside.add('No Group'); }, child: const Text('No Group'), ), TapRegion( groupId: 1, onTapOutside: (PointerEvent event) { tappedOutside.add('Group 1 A'); }, child: const Text('Group 1 A'), ), TapRegion( groupId: 1, onTapOutside: (PointerEvent event) { tappedOutside.add('Group 1 B'); }, child: const Text('Group 1 B'), ), ], ), ), ], ), ), ); await tester.pump(); Future<void> click(Finder finder) async { final TestGesture gesture = await tester.startGesture( tester.getCenter(finder), kind: PointerDeviceKind.mouse, ); await gesture.up(); await gesture.removePointer(); } expect(tappedOutside, isEmpty); await click(find.text('No Group')); expect( tappedOutside, unorderedEquals(<String>{ 'Group 1 A', 'Group 1 B', })); tappedOutside.clear(); await click(find.text('Group 1 A')); expect( tappedOutside, equals(<String>{ 'No Group', })); tappedOutside.clear(); await click(find.text('Group 1 B')); expect( tappedOutside, equals(<String>{ 'No Group', })); tappedOutside.clear(); await click(find.text('Outside')); expect( tappedOutside, unorderedEquals(<String>{ 'No Group', 'Group 1 A', 'Group 1 B', })); tappedOutside.clear(); await click(find.text('Outside Surface')); expect(tappedOutside, isEmpty); }); testWidgetsWithLeakTracking('TapRegionSurface consumes outside taps when asked', (WidgetTester tester) async { final Set<String> tappedOutside = <String>{}; int propagatedTaps = 0; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Column( children: <Widget>[ const Text('Outside Surface'), TapRegionSurface( child: Row( children: <Widget>[ GestureDetector( onTap: () { propagatedTaps += 1; }, child: const Text('Outside'), ), TapRegion( consumeOutsideTaps: true, onTapOutside: (PointerEvent event) { tappedOutside.add('No Group'); }, child: const Text('No Group'), ), TapRegion( groupId: 1, onTapOutside: (PointerEvent event) { tappedOutside.add('Group 1 A'); }, child: const Text('Group 1 A'), ), TapRegion( groupId: 1, consumeOutsideTaps: true, onTapOutside: (PointerEvent event) { tappedOutside.add('Group 1 B'); }, child: const Text('Group 1 B'), ), ], ), ), ], ), ), ); await tester.pump(); Future<void> click(Finder finder) async { final TestGesture gesture = await tester.startGesture( tester.getCenter(finder), kind: PointerDeviceKind.mouse, ); await gesture.up(); await gesture.removePointer(); } expect(tappedOutside, isEmpty); expect(propagatedTaps, equals(0)); await click(find.text('No Group')); expect( tappedOutside, unorderedEquals(<String>{ 'Group 1 A', 'Group 1 B', })); expect(propagatedTaps, equals(0)); tappedOutside.clear(); await click(find.text('Group 1 A')); expect( tappedOutside, equals(<String>{ 'No Group', })); expect(propagatedTaps, equals(0)); tappedOutside.clear(); await click(find.text('Group 1 B')); expect( tappedOutside, equals(<String>{ 'No Group', })); expect(propagatedTaps, equals(0)); tappedOutside.clear(); await click(find.text('Outside')); expect( tappedOutside, unorderedEquals(<String>{ 'No Group', 'Group 1 A', 'Group 1 B', })); expect(propagatedTaps, equals(0)); tappedOutside.clear(); await click(find.text('Outside Surface')); expect(tappedOutside, isEmpty); }); testWidgetsWithLeakTracking('TapRegionSurface detects inside taps', (WidgetTester tester) async { final Set<String> tappedInside = <String>{}; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Column( children: <Widget>[ const Text('Outside Surface'), TapRegionSurface( child: Row( children: <Widget>[ const Text('Outside'), TapRegion( onTapInside: (PointerEvent event) { tappedInside.add('No Group'); }, child: const Text('No Group'), ), TapRegion( groupId: 1, onTapInside: (PointerEvent event) { tappedInside.add('Group 1 A'); }, child: const Text('Group 1 A'), ), TapRegion( groupId: 1, onTapInside: (PointerEvent event) { tappedInside.add('Group 1 B'); }, child: const Text('Group 1 B'), ), ], ), ), ], ), ), ); await tester.pump(); Future<void> click(Finder finder) async { final TestGesture gesture = await tester.startGesture( tester.getCenter(finder), kind: PointerDeviceKind.mouse, ); await gesture.up(); await gesture.removePointer(); } expect(tappedInside, isEmpty); await click(find.text('No Group')); expect( tappedInside, unorderedEquals(<String>{ 'No Group', })); tappedInside.clear(); await click(find.text('Group 1 A')); expect( tappedInside, equals(<String>{ 'Group 1 A', 'Group 1 B', })); tappedInside.clear(); await click(find.text('Group 1 B')); expect( tappedInside, equals(<String>{ 'Group 1 A', 'Group 1 B', })); tappedInside.clear(); await click(find.text('Outside')); expect(tappedInside, isEmpty); tappedInside.clear(); await click(find.text('Outside Surface')); expect(tappedInside, isEmpty); }); testWidgetsWithLeakTracking('TapRegionSurface detects inside taps correctly with behavior', (WidgetTester tester) async { final Set<String> tappedInside = <String>{}; const ValueKey<String> noGroupKey = ValueKey<String>('No Group'); const ValueKey<String> group1AKey = ValueKey<String>('Group 1 A'); const ValueKey<String> group1BKey = ValueKey<String>('Group 1 B'); await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Column( children: <Widget>[ const Text('Outside Surface'), TapRegionSurface( child: Row( children: <Widget>[ ConstrainedBox( constraints: const BoxConstraints.tightFor(width: 100, height: 100), child: TapRegion( onTapInside: (PointerEvent event) { tappedInside.add(noGroupKey.value); }, child: const Stack(key: noGroupKey), ), ), ConstrainedBox( constraints: const BoxConstraints.tightFor(width: 100, height: 100), child: TapRegion( groupId: 1, behavior: HitTestBehavior.opaque, onTapInside: (PointerEvent event) { tappedInside.add(group1AKey.value); }, child: const Stack(key: group1AKey), ), ), ConstrainedBox( constraints: const BoxConstraints.tightFor(width: 100, height: 100), child: TapRegion( groupId: 1, behavior: HitTestBehavior.translucent, onTapInside: (PointerEvent event) { tappedInside.add(group1BKey.value); }, child: const Stack(key: group1BKey), ), ), ], ), ), ], ), ), ); await tester.pump(); Future<void> click(Finder finder) async { final TestGesture gesture = await tester.startGesture( tester.getCenter(finder), kind: PointerDeviceKind.mouse, ); await gesture.up(); await gesture.removePointer(); } expect(tappedInside, isEmpty); await click(find.byKey(noGroupKey)); expect(tappedInside, isEmpty); // No hittable children, so no hit. await click(find.byKey(group1AKey)); // No hittable children, but set to opaque, so it hits, triggering the // group. expect( tappedInside, equals(<String>{ 'Group 1 A', 'Group 1 B', }), ); tappedInside.clear(); await click(find.byKey(group1BKey)); expect(tappedInside, isEmpty); // No hittable children while translucent, so no hit. tappedInside.clear(); }); testWidgetsWithLeakTracking('Setting the group updates the registration', (WidgetTester tester) async { final Set<String> tappedOutside = <String>{}; await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: TapRegionSurface( child: Row( children: <Widget>[ const Text('Outside'), TapRegion( groupId: 1, onTapOutside: (PointerEvent event) { tappedOutside.add('Group 1 A'); }, child: const Text('Group 1 A'), ), TapRegion( groupId: 1, onTapOutside: (PointerEvent event) { tappedOutside.add('Group 1 B'); }, child: const Text('Group 1 B'), ), ], ), ), ), ); await tester.pump(); Future<void> click(Finder finder) async { final TestGesture gesture = await tester.startGesture( tester.getCenter(finder), kind: PointerDeviceKind.mouse, ); await gesture.up(); await gesture.removePointer(); } expect(tappedOutside, isEmpty); await click(find.text('Group 1 A')); expect(tappedOutside, isEmpty); await click(find.text('Group 1 B')); expect(tappedOutside, isEmpty); await click(find.text('Outside')); expect( tappedOutside, equals(<String>[ 'Group 1 A', 'Group 1 B', ])); tappedOutside.clear(); // Now change out the groups. await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: TapRegionSurface( child: Row( children: <Widget>[ const Text('Outside'), TapRegion( groupId: 1, onTapOutside: (PointerEvent event) { tappedOutside.add('Group 1 A'); }, child: const Text('Group 1 A'), ), TapRegion( groupId: 2, onTapOutside: (PointerEvent event) { tappedOutside.add('Group 2 A'); }, child: const Text('Group 2 A'), ), ], ), ), ), ); await click(find.text('Group 1 A')); expect(tappedOutside, equals(<String>['Group 2 A'])); tappedOutside.clear(); await click(find.text('Group 2 A')); expect(tappedOutside, equals(<String>['Group 1 A'])); tappedOutside.clear(); await click(find.text('Outside')); expect( tappedOutside, equals(<String>[ 'Group 1 A', 'Group 2 A', ])); tappedOutside.clear(); }); }