blob: 3336a6feb6e7db3c6a70ce7a6970a5596a599cb8 [file] [log] [blame]
// 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.
// TODO(gspencergoog): Remove this tag once this test's state leaks/test
// dependencies have been fixed.
// https://github.com/flutter/flutter/issues/85160
// Fails with "flutter test --test-randomize-ordering-seed=123"
@Tags(<String>['no-shuffle'])
library;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'semantics_tester.dart';
void main() {
testWidgets('Drag and drop - control test', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
int dragStartedCount = 0;
int moveCount = 0;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragStarted: () {
++dragStartedCount;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onMove: (_) => moveCount++,
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(dragStartedCount, 0);
expect(moveCount, 0);
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(dragStartedCount, 1);
expect(moveCount, 0);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(dragStartedCount, 1);
expect(moveCount, 1);
await gesture.up();
await tester.pump();
expect(accepted, equals(<int>[1]));
expect(acceptedDetails, hasLength(1));
expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(dragStartedCount, 1);
expect(moveCount, 1);
});
// Regression test for https://github.com/flutter/flutter/issues/76825
testWidgets('Drag and drop - onLeave callback fires correctly with generic parameter', (WidgetTester tester) async {
final Map<String,int> leftBehind = <String,int>{
'Target 1': 0,
'Target 2': 0,
};
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target 1'));
},
onLeave: (int? data) {
if (data != null) {
leftBehind['Target 1'] = leftBehind['Target 1']! + data;
}
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target 2'));
},
onLeave: (int? data) {
if (data != null) {
leftBehind['Target 2'] = leftBehind['Target 2']! + data;
}
},
),
],
),
));
expect(leftBehind['Target 1'], equals(0));
expect(leftBehind['Target 2'], equals(0));
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(leftBehind['Target 1'], equals(0));
expect(leftBehind['Target 2'], equals(0));
final Offset secondLocation = tester.getCenter(find.text('Target 1'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(leftBehind['Target 1'], equals(0));
expect(leftBehind['Target 2'], equals(0));
final Offset thirdLocation = tester.getCenter(find.text('Target 2'));
await gesture.moveTo(thirdLocation);
await tester.pump();
expect(leftBehind['Target 1'], equals(1));
expect(leftBehind['Target 2'], equals(0));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(leftBehind['Target 1'], equals(1));
expect(leftBehind['Target 2'], equals(1));
await gesture.up();
await tester.pump();
expect(leftBehind['Target 1'], equals(1));
expect(leftBehind['Target 2'], equals(1));
});
testWidgets('Drag and drop - onLeave callback fires correctly', (WidgetTester tester) async {
final Map<String,int> leftBehind = <String,int>{
'Target 1': 0,
'Target 2': 0,
};
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target 1'));
},
onLeave: (Object? data) {
if (data is int) {
leftBehind['Target 1'] = leftBehind['Target 1']! + data;
}
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target 2'));
},
onLeave: (Object? data) {
if (data is int) {
leftBehind['Target 2'] = leftBehind['Target 2']! + data;
}
},
),
],
),
));
expect(leftBehind['Target 1'], equals(0));
expect(leftBehind['Target 2'], equals(0));
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(leftBehind['Target 1'], equals(0));
expect(leftBehind['Target 2'], equals(0));
final Offset secondLocation = tester.getCenter(find.text('Target 1'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(leftBehind['Target 1'], equals(0));
expect(leftBehind['Target 2'], equals(0));
final Offset thirdLocation = tester.getCenter(find.text('Target 2'));
await gesture.moveTo(thirdLocation);
await tester.pump();
expect(leftBehind['Target 1'], equals(1));
expect(leftBehind['Target 2'], equals(0));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(leftBehind['Target 1'], equals(1));
expect(leftBehind['Target 2'], equals(1));
await gesture.up();
await tester.pump();
expect(leftBehind['Target 1'], equals(1));
expect(leftBehind['Target 2'], equals(1));
});
// Regression test for https://github.com/flutter/flutter/issues/76825
testWidgets('Drag and drop - onMove callback fires correctly with generic parameter', (WidgetTester tester) async {
final Map<String,int> targetMoveCount = <String,int>{
'Target 1': 0,
'Target 2': 0,
};
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target 1'));
},
onMove: (DragTargetDetails<int> details) {
targetMoveCount['Target 1'] =
targetMoveCount['Target 1']! + details.data;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target 2'));
},
onMove: (DragTargetDetails<int> details) {
targetMoveCount['Target 2'] =
targetMoveCount['Target 2']! + details.data;
},
),
],
),
));
expect(targetMoveCount['Target 1'], equals(0));
expect(targetMoveCount['Target 2'], equals(0));
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(targetMoveCount['Target 1'], equals(0));
expect(targetMoveCount['Target 2'], equals(0));
final Offset secondLocation = tester.getCenter(find.text('Target 1'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(targetMoveCount['Target 1'], equals(1));
expect(targetMoveCount['Target 2'], equals(0));
final Offset thirdLocation = tester.getCenter(find.text('Target 2'));
await gesture.moveTo(thirdLocation);
await tester.pump();
expect(targetMoveCount['Target 1'], equals(1));
expect(targetMoveCount['Target 2'], equals(1));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(targetMoveCount['Target 1'], equals(2));
expect(targetMoveCount['Target 2'], equals(1));
await gesture.up();
await tester.pump();
expect(targetMoveCount['Target 1'], equals(2));
expect(targetMoveCount['Target 2'], equals(1));
});
testWidgets('Drag and drop - onMove callback fires correctly', (WidgetTester tester) async {
final Map<String,int> targetMoveCount = <String,int>{
'Target 1': 0,
'Target 2': 0,
};
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target 1'));
},
onMove: (DragTargetDetails<dynamic> details) {
if (details.data is int) {
targetMoveCount['Target 1'] =
targetMoveCount['Target 1']! + (details.data as int);
}
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target 2'));
},
onMove: (DragTargetDetails<dynamic> details) {
if (details.data is int) {
targetMoveCount['Target 2'] =
targetMoveCount['Target 2']! + (details.data as int);
}
},
),
],
),
));
expect(targetMoveCount['Target 1'], equals(0));
expect(targetMoveCount['Target 2'], equals(0));
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(targetMoveCount['Target 1'], equals(0));
expect(targetMoveCount['Target 2'], equals(0));
final Offset secondLocation = tester.getCenter(find.text('Target 1'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(targetMoveCount['Target 1'], equals(1));
expect(targetMoveCount['Target 2'], equals(0));
final Offset thirdLocation = tester.getCenter(find.text('Target 2'));
await gesture.moveTo(thirdLocation);
await tester.pump();
expect(targetMoveCount['Target 1'], equals(1));
expect(targetMoveCount['Target 2'], equals(1));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(targetMoveCount['Target 1'], equals(2));
expect(targetMoveCount['Target 2'], equals(1));
await gesture.up();
await tester.pump();
expect(targetMoveCount['Target 1'], equals(2));
expect(targetMoveCount['Target 2'], equals(1));
});
testWidgets('Drag and drop - dragging over button', (WidgetTester tester) async {
final List<String> events = <String>[];
Offset firstLocation, secondLocation;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
Stack(
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
events.add('tap');
},
child: const Text('Button'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const IgnorePointer(
child: Text('Target'),
);
},
onAccept: (int? data) {
events.add('drop');
},
onAcceptWithDetails: (DragTargetDetails<int> _) {
events.add('details');
},
),
],
),
],
),
));
expect(events, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(find.text('Button'), findsOneWidget);
// taps (we check both to make sure the test is consistent)
expect(events, isEmpty);
await tester.tap(find.text('Button'));
expect(events, equals(<String>['tap']));
events.clear();
expect(events, isEmpty);
await tester.tap(find.text('Target'), warnIfMissed: false); // (inside IgnorePointer)
expect(events, equals(<String>['tap']));
events.clear();
// drag and drop
firstLocation = tester.getCenter(find.text('Source'));
TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(events, isEmpty);
await gesture.up();
await tester.pump();
expect(events, equals(<String>['drop', 'details']));
events.clear();
// drag and tap and drop
firstLocation = tester.getCenter(find.text('Source'));
gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(events, isEmpty);
await tester.tap(find.text('Button'));
await tester.tap(find.text('Target'), warnIfMissed: false); // (inside IgnorePointer)
await gesture.up();
await tester.pump();
expect(events, equals(<String>['tap', 'tap', 'drop', 'details']));
events.clear();
});
testWidgets('Drag and drop - tapping button', (WidgetTester tester) async {
final List<String> events = <String>[];
Offset firstLocation, secondLocation;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
events.add('tap');
},
child: const Text('Button'),
),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const Text('Target');
},
onAccept: (int? data) {
events.add('drop');
},
onAcceptWithDetails: (DragTargetDetails<int> _) {
events.add('details');
},
),
],
),
));
expect(events, isEmpty);
expect(find.text('Button'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(events, isEmpty);
await tester.tap(find.text('Button'));
expect(events, equals(<String>['tap']));
events.clear();
firstLocation = tester.getCenter(find.text('Button'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(events, isEmpty);
await gesture.up();
await tester.pump();
expect(events, equals(<String>['drop', 'details']));
events.clear();
});
testWidgets('Drag and drop - long press draggable, short press', (WidgetTester tester) async {
final List<String> events = <String>[];
Offset firstLocation, secondLocation;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const LongPressDraggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const Text('Target');
},
onAccept: (int? data) {
events.add('drop');
},
onAcceptWithDetails: (DragTargetDetails<int> _) {
events.add('details');
},
),
],
),
));
expect(events, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(events, isEmpty);
await tester.tap(find.text('Source'));
expect(events, isEmpty);
firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(events, isEmpty);
await gesture.up();
await tester.pump();
expect(events, isEmpty);
});
testWidgets('Drag and drop - long press draggable, long press', (WidgetTester tester) async {
final List<String> events = <String>[];
Offset firstLocation, secondLocation;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const Text('Target');
},
onAccept: (int? data) {
events.add('drop');
},
onAcceptWithDetails: (DragTargetDetails<int> _) {
events.add('details');
},
),
],
),
));
expect(events, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(events, isEmpty);
await tester.tap(find.text('Source'));
expect(events, isEmpty);
firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await tester.pump(const Duration(seconds: 20));
secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(events, isEmpty);
await gesture.up();
await tester.pump();
expect(events, equals(<String>['drop', 'details']));
});
testWidgets('Drag and drop - horizontal and vertical draggables in vertical block', (WidgetTester tester) async {
final List<String> events = <String>[];
Offset firstLocation, secondLocation, thirdLocation;
await tester.pumpWidget(MaterialApp(
home: ListView(
dragStartBehavior: DragStartBehavior.down,
children: <Widget>[
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const Text('Target');
},
onAccept: (int? data) {
events.add('drop $data');
},
onAcceptWithDetails: (DragTargetDetails<int> _) {
events.add('details');
},
),
Container(height: 400.0),
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
affinity: Axis.horizontal,
child: Text('H'),
),
const Draggable<int>(
data: 2,
feedback: Text('Dragging'),
affinity: Axis.vertical,
child: Text('V'),
),
Container(height: 500.0),
Container(height: 500.0),
Container(height: 500.0),
Container(height: 500.0),
],
),
));
expect(events, isEmpty);
expect(find.text('Target'), findsOneWidget);
expect(find.text('H'), findsOneWidget);
expect(find.text('V'), findsOneWidget);
// vertical draggable drags vertically
expect(events, isEmpty);
firstLocation = tester.getCenter(find.text('V'));
secondLocation = tester.getCenter(find.text('Target'));
TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump();
await gesture.up();
await tester.pump();
expect(events, equals(<String>['drop 2', 'details']));
expect(tester.getCenter(find.text('Target')).dy, greaterThan(0.0));
events.clear();
// horizontal draggable drags horizontally
expect(events, isEmpty);
firstLocation = tester.getTopLeft(find.text('H'));
secondLocation = tester.getTopRight(find.text('H'));
thirdLocation = tester.getCenter(find.text('Target'));
gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump();
await gesture.moveTo(thirdLocation);
await tester.pump();
await gesture.up();
await tester.pump();
expect(events, equals(<String>['drop 1', 'details']));
expect(tester.getCenter(find.text('Target')).dy, greaterThan(0.0));
events.clear();
// vertical draggable drags horizontally when there's no competition
// from other gesture detectors
expect(events, isEmpty);
firstLocation = tester.getTopLeft(find.text('V'));
secondLocation = tester.getTopRight(find.text('V'));
thirdLocation = tester.getCenter(find.text('Target'));
gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump();
await gesture.moveTo(thirdLocation);
await tester.pump();
await gesture.up();
await tester.pump();
expect(events, equals(<String>['drop 2', 'details']));
expect(tester.getCenter(find.text('Target')).dy, greaterThan(0.0));
events.clear();
// horizontal draggable doesn't drag vertically when there is competition
// for vertical gestures
expect(events, isEmpty);
firstLocation = tester.getCenter(find.text('H'));
secondLocation = tester.getCenter(find.text('Target'));
gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump(); // scrolls off screen!
await gesture.up();
await tester.pump();
expect(events, equals(<String>[]));
expect(find.text('Target'), findsNothing);
events.clear();
});
testWidgets('Drag and drop - horizontal and vertical draggables in horizontal block', (WidgetTester tester) async {
final List<String> events = <String>[];
Offset firstLocation, secondLocation, thirdLocation;
await tester.pumpWidget(MaterialApp(
home: ListView(
dragStartBehavior: DragStartBehavior.down,
scrollDirection: Axis.horizontal,
children: <Widget>[
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const Text('Target');
},
onAccept: (int? data) {
events.add('drop $data');
},
onAcceptWithDetails: (DragTargetDetails<int> _) {
events.add('details');
},
),
Container(width: 400.0),
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
affinity: Axis.horizontal,
child: Text('H'),
),
const Draggable<int>(
data: 2,
feedback: Text('Dragging'),
affinity: Axis.vertical,
child: Text('V'),
),
Container(width: 500.0),
Container(width: 500.0),
Container(width: 500.0),
Container(width: 500.0),
],
),
));
expect(events, isEmpty);
expect(find.text('Target'), findsOneWidget);
expect(find.text('H'), findsOneWidget);
expect(find.text('V'), findsOneWidget);
// horizontal draggable drags horizontally
expect(events, isEmpty);
firstLocation = tester.getCenter(find.text('H'));
secondLocation = tester.getCenter(find.text('Target'));
TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump();
await gesture.up();
await tester.pump();
expect(events, equals(<String>['drop 1', 'details']));
expect(tester.getCenter(find.text('Target')).dx, greaterThan(0.0));
events.clear();
// vertical draggable drags vertically
expect(events, isEmpty);
firstLocation = tester.getTopLeft(find.text('V'));
secondLocation = tester.getBottomLeft(find.text('V'));
thirdLocation = tester.getCenter(find.text('Target'));
gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump();
await gesture.moveTo(thirdLocation);
await tester.pump();
await gesture.up();
await tester.pump();
expect(events, equals(<String>['drop 2', 'details']));
expect(tester.getCenter(find.text('Target')).dx, greaterThan(0.0));
events.clear();
// horizontal draggable drags vertically when there's no competition
// from other gesture detectors
expect(events, isEmpty);
firstLocation = tester.getTopLeft(find.text('H'));
secondLocation = tester.getBottomLeft(find.text('H'));
thirdLocation = tester.getCenter(find.text('Target'));
gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump();
await gesture.moveTo(thirdLocation);
await tester.pump();
await gesture.up();
await tester.pump();
expect(events, equals(<String>['drop 1', 'details']));
expect(tester.getCenter(find.text('Target')).dx, greaterThan(0.0));
events.clear();
// vertical draggable doesn't drag horizontally when there is competition
// for horizontal gestures
expect(events, isEmpty);
firstLocation = tester.getCenter(find.text('V'));
secondLocation = tester.getCenter(find.text('Target'));
gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump(); // scrolls off screen!
await gesture.up();
await tester.pump();
expect(events, equals(<String>[]));
expect(find.text('Target'), findsNothing);
events.clear();
});
group('Drag and drop - Draggables with a set axis only move along that axis', () {
final List<String> events = <String>[];
Widget build() {
return MaterialApp(
home: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const Text('Target');
},
onAccept: (int? data) {
events.add('drop $data');
},
onAcceptWithDetails: (DragTargetDetails<int> _) {
events.add('details');
},
),
Container(width: 400.0),
const Draggable<int>(
data: 1,
feedback: Text('H'),
childWhenDragging: SizedBox(),
axis: Axis.horizontal,
child: Text('H'),
),
const Draggable<int>(
data: 2,
feedback: Text('V'),
childWhenDragging: SizedBox(),
axis: Axis.vertical,
child: Text('V'),
),
const Draggable<int>(
data: 3,
feedback: Text('N'),
childWhenDragging: SizedBox(),
child: Text('N'),
),
Container(width: 500.0),
Container(width: 500.0),
Container(width: 500.0),
Container(width: 500.0),
],
),
);
}
testWidgets('Null axis draggable moves along all axes', (WidgetTester tester) async {
await tester.pumpWidget(build());
final Offset firstLocation = tester.getTopLeft(find.text('N'));
final Offset secondLocation = firstLocation + const Offset(300.0, 300.0);
final Offset thirdLocation = firstLocation + const Offset(-300.0, -300.0);
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('N')), secondLocation);
await gesture.moveTo(thirdLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('N')), thirdLocation);
});
testWidgets('Horizontal axis draggable moves horizontally', (WidgetTester tester) async {
await tester.pumpWidget(build());
final Offset firstLocation = tester.getTopLeft(find.text('H'));
final Offset secondLocation = firstLocation + const Offset(300.0, 0.0);
final Offset thirdLocation = firstLocation + const Offset(-300.0, 0.0);
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('H')), secondLocation);
await gesture.moveTo(thirdLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('H')), thirdLocation);
});
testWidgets('Horizontal axis draggable does not move vertically', (WidgetTester tester) async {
await tester.pumpWidget(build());
final Offset firstLocation = tester.getTopLeft(find.text('H'));
final Offset secondDragLocation = firstLocation + const Offset(300.0, 200.0);
// The horizontal drag widget won't scroll vertically.
final Offset secondWidgetLocation = firstLocation + const Offset(300.0, 0.0);
final Offset thirdDragLocation = firstLocation + const Offset(-300.0, -200.0);
final Offset thirdWidgetLocation = firstLocation + const Offset(-300.0, 0.0);
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondDragLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('H')), secondWidgetLocation);
await gesture.moveTo(thirdDragLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('H')), thirdWidgetLocation);
});
testWidgets('Vertical axis draggable moves vertically', (WidgetTester tester) async {
await tester.pumpWidget(build());
final Offset firstLocation = tester.getTopLeft(find.text('V'));
final Offset secondLocation = firstLocation + const Offset(0.0, 300.0);
final Offset thirdLocation = firstLocation + const Offset(0.0, -300.0);
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('V')), secondLocation);
await gesture.moveTo(thirdLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('V')), thirdLocation);
});
testWidgets('Vertical axis draggable does not move horizontally', (WidgetTester tester) async {
await tester.pumpWidget(build());
final Offset firstLocation = tester.getTopLeft(find.text('V'));
final Offset secondDragLocation = firstLocation + const Offset(200.0, 300.0);
// The vertical drag widget won't scroll horizontally.
final Offset secondWidgetLocation = firstLocation + const Offset(0.0, 300.0);
final Offset thirdDragLocation = firstLocation + const Offset(-200.0, -300.0);
final Offset thirdWidgetLocation = firstLocation + const Offset(0.0, -300.0);
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await gesture.moveTo(secondDragLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('V')), secondWidgetLocation);
await gesture.moveTo(thirdDragLocation);
await tester.pump();
expect(tester.getTopLeft(find.text('V')), thirdWidgetLocation);
});
});
group('Drag and drop - onDragUpdate called if draggable moves along a set axis', () {
int updated = 0;
Offset dragDelta = Offset.zero;
setUp(() {
updated = 0;
dragDelta = Offset.zero;
});
Widget build() {
return MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragUpdate: (DragUpdateDetails details) {
dragDelta += details.delta;
updated++;
},
child: const Text('Source'),
),
Draggable<int>(
data: 2,
feedback: const Text('Vertical Dragging'),
onDragUpdate: (DragUpdateDetails details) {
dragDelta += details.delta;
updated++;
},
axis: Axis.vertical,
child: const Text('Vertical Source'),
),
Draggable<int>(
data: 3,
feedback: const Text('Horizontal Dragging'),
onDragUpdate: (DragUpdateDetails details) {
dragDelta += details.delta;
updated++;
},
axis: Axis.horizontal,
child: const Text('Horizontal Source'),
),
],
),
);
}
testWidgets('Null axis onDragUpdate called only if draggable moves in any direction', (WidgetTester tester) async {
await tester.pumpWidget(build());
expect(updated, 0);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(updated, 0);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
await gesture.moveBy(const Offset(10, 10));
await tester.pump();
expect(updated, 1);
await gesture.moveBy(Offset.zero);
await tester.pump();
expect(updated, 1);
await gesture.up();
await tester.pump();
expect(updated, 1);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(dragDelta.dx, 10);
expect(dragDelta.dy, 10);
});
testWidgets('Vertical axis onDragUpdate only called if draggable moves vertical', (WidgetTester tester) async {
await tester.pumpWidget(build());
expect(updated, 0);
expect(find.text('Vertical Source'), findsOneWidget);
expect(find.text('Vertical Dragging'), findsNothing);
final Offset firstLocation = tester.getCenter(find.text('Vertical Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(updated, 0);
expect(find.text('Vertical Source'), findsOneWidget);
expect(find.text('Vertical Dragging'), findsOneWidget);
await gesture.moveBy(const Offset(0, 10));
await tester.pump();
expect(updated, 1);
await gesture.moveBy(const Offset(10 , 0));
await tester.pump();
expect(updated, 1);
await gesture.up();
await tester.pump();
expect(updated, 1);
expect(find.text('Vertical Source'), findsOneWidget);
expect(find.text('Vertical Dragging'), findsNothing);
expect(dragDelta.dx, 0);
expect(dragDelta.dy, 10);
});
testWidgets('Horizontal axis onDragUpdate only called if draggable moves horizontal', (WidgetTester tester) async {
await tester.pumpWidget(build());
expect(updated, 0);
expect(find.text('Horizontal Source'), findsOneWidget);
expect(find.text('Horizontal Dragging'), findsNothing);
final Offset firstLocation = tester.getCenter(find.text('Horizontal Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(updated, 0);
expect(find.text('Horizontal Source'), findsOneWidget);
expect(find.text('Horizontal Dragging'), findsOneWidget);
await gesture.moveBy(const Offset(0, 10));
await tester.pump();
expect(updated, 0);
await gesture.moveBy(const Offset(10 , 0));
await tester.pump();
expect(updated, 1);
await gesture.up();
await tester.pump();
expect(updated, 1);
expect(find.text('Horizontal Source'), findsOneWidget);
expect(find.text('Horizontal Dragging'), findsNothing);
expect(dragDelta.dx, 10);
expect(dragDelta.dy, 0);
});
});
testWidgets('Drag and drop - onDraggableCanceled not called if dropped on accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDraggableCanceledCalled = false;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDraggableCanceled: (Velocity velocity, Offset offset) {
onDraggableCanceledCalled = true;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isFalse);
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isFalse);
await gesture.up();
await tester.pump();
expect(accepted, equals(<int>[1]));
expect(acceptedDetails, hasLength(1));
expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isFalse);
});
testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDraggableCanceledCalled = false;
late Velocity onDraggableCanceledVelocity;
late Offset onDraggableCanceledOffset;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDraggableCanceled: (Velocity velocity, Offset offset) {
onDraggableCanceledCalled = true;
onDraggableCanceledVelocity = velocity;
onDraggableCanceledOffset = offset;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(
height: 100.0,
child: Text('Target'),
);
},
onWillAccept: (int? data) => false,
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isFalse);
final Offset firstLocation = tester.getTopLeft(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isFalse);
await gesture.up();
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isTrue);
expect(onDraggableCanceledVelocity, equals(Velocity.zero));
expect(onDraggableCanceledOffset, equals(Offset(secondLocation.dx, secondLocation.dy)));
});
testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with correct velocity', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDraggableCanceledCalled = false;
late Velocity onDraggableCanceledVelocity;
late Offset onDraggableCanceledOffset;
await tester.pumpWidget(MaterialApp(
home: Column(children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Source'),
onDraggableCanceled: (Velocity velocity, Offset offset) {
onDraggableCanceledCalled = true;
onDraggableCanceledVelocity = velocity;
onDraggableCanceledOffset = offset;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onWillAccept: (int? data) => false,
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
]),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isFalse);
final Offset flingStart = tester.getTopLeft(find.text('Source'));
await tester.flingFrom(flingStart, const Offset(0.0, 100.0), 1000.0);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isTrue);
expect(onDraggableCanceledVelocity.pixelsPerSecond.dx.abs(), lessThan(0.0000001));
expect((onDraggableCanceledVelocity.pixelsPerSecond.dy - 1000.0).abs(), lessThan(0.0000001));
expect(onDraggableCanceledOffset, equals(Offset(flingStart.dx, flingStart.dy) + const Offset(0.0, 100.0)));
});
testWidgets('Drag and drop - onDragEnd not called if dropped on non-accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDragEndCalled = false;
late DraggableDetails onDragEndDraggableDetails;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragEnd: (DraggableDetails details) {
onDragEndCalled = true;
onDragEndDraggableDetails = details;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onWillAccept: (int? data) => false,
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
final Offset firstLocation = tester.getTopLeft(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
await gesture.up();
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isTrue);
expect(onDragEndDraggableDetails, isNotNull);
expect(onDragEndDraggableDetails.wasAccepted, isFalse);
expect(onDragEndDraggableDetails.velocity, equals(Velocity.zero));
expect(
onDragEndDraggableDetails.offset,
equals(Offset(secondLocation.dx, secondLocation.dy - firstLocation.dy)),
);
});
testWidgets('Drag and drop - DragTarget rebuilds with and without rejected data when a rejected draggable enters and leaves', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return SizedBox(
height: 100.0,
child: rejects.isNotEmpty
? const Text('Rejected')
: const Text('Target'),
);
},
onWillAccept: (int? data) => false,
),
],
),
));
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(find.text('Rejected'), findsNothing);
final Offset firstLocation = tester.getTopLeft(find.text('Source'));
final TestGesture gesture =
await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(find.text('Rejected'), findsNothing);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsNothing);
expect(find.text('Rejected'), findsOneWidget);
await gesture.moveTo(firstLocation);
await tester.pump();
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(find.text('Rejected'), findsNothing);
});
testWidgets('Drag and drop - Can drag and drop over a non-accepting target multiple times', (WidgetTester tester) async {
int numberOfTimesOnDraggableCanceledCalled = 0;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDraggableCanceled: (Velocity velocity, Offset offset) {
numberOfTimesOnDraggableCanceledCalled++;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return SizedBox(
height: 100.0,
child: rejects.isNotEmpty
? const Text('Rejected')
: const Text('Target'),
);
},
onWillAccept: (int? data) => false,
),
],
),
));
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(find.text('Rejected'), findsNothing);
final Offset firstLocation = tester.getTopLeft(find.text('Source'));
final TestGesture gesture =
await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(find.text('Rejected'), findsNothing);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsNothing);
expect(find.text('Rejected'), findsOneWidget);
await gesture.up();
await tester.pump();
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(find.text('Rejected'), findsNothing);
expect(numberOfTimesOnDraggableCanceledCalled, 1);
// Drag and drop the Draggable onto the Target a second time.
final TestGesture secondGesture =
await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(find.text('Rejected'), findsNothing);
await secondGesture.moveTo(secondLocation);
await tester.pump();
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsNothing);
expect(find.text('Rejected'), findsOneWidget);
await secondGesture.up();
await tester.pump();
expect(numberOfTimesOnDraggableCanceledCalled, 2);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(find.text('Rejected'), findsNothing);
});
testWidgets('Drag and drop - onDragCompleted not called if dropped on non-accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDragCompletedCalled = false;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragCompleted: () {
onDragCompletedCalled = true;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(
height: 100.0,
child: Text('Target'),
);
},
onWillAccept: (int? data) => false,
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset firstLocation = tester.getTopLeft(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
await gesture.up();
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
});
testWidgets('Drag and drop - onDragEnd called if dropped on accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDragEndCalled = false;
late DraggableDetails onDragEndDraggableDetails;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragEnd: (DraggableDetails details) {
onDragEndCalled = true;
onDragEndDraggableDetails = details;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
await gesture.up();
await tester.pump();
final Offset droppedLocation = tester.getTopLeft(find.text('Target'));
final Offset expectedDropOffset = Offset(droppedLocation.dx, secondLocation.dy - firstLocation.dy);
expect(accepted, equals(<int>[1]));
expect(acceptedDetails, hasLength(1));
expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isTrue);
expect(onDragEndDraggableDetails, isNotNull);
expect(onDragEndDraggableDetails.wasAccepted, isTrue);
expect(onDragEndDraggableDetails.velocity, equals(Velocity.zero));
expect(onDragEndDraggableDetails.offset, equals(expectedDropOffset));
});
testWidgets('DragTarget does not call onDragEnd when remove from the tree', (WidgetTester tester) async {
final List<String> events = <String>[];
Offset firstLocation, secondLocation;
int timesOnDragEndCalled = 0;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragEnd: (DraggableDetails details) {
timesOnDragEndCalled++;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const Text('Target');
},
onAccept: (int? data) {
events.add('drop');
},
onAcceptWithDetails: (DragTargetDetails<int> _) {
events.add('details');
},
),
],
),
));
expect(events, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(events, isEmpty);
await tester.tap(find.text('Source'));
expect(events, isEmpty);
firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await tester.pump(const Duration(seconds: 20));
secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
await tester.pumpWidget(const MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
],
),
));
expect(events, isEmpty);
expect(timesOnDragEndCalled, equals(1));
await gesture.up();
await tester.pump();
});
testWidgets('Drag and drop - onDragCompleted called if dropped on accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDragCompletedCalled = false;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragCompleted: () {
onDragCompletedCalled = true;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
await gesture.up();
await tester.pump();
expect(accepted, equals(<int>[1]));
expect(acceptedDetails, hasLength(1));
expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isTrue);
});
testWidgets('Drag and drop - allow pass through of unaccepted data test', (WidgetTester tester) async {
final List<int> acceptedInts = <int>[];
final List<DragTargetDetails<int>> acceptedIntsDetails = <DragTargetDetails<int>>[];
final List<double> acceptedDoubles = <double>[];
final List<DragTargetDetails<double>> acceptedDoublesDetails = <DragTargetDetails<double>>[];
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(
data: 1,
feedback: Text('IntDragging'),
child: Text('IntSource'),
),
const Draggable<double>(
data: 1.0,
feedback: Text('DoubleDragging'),
child: Text('DoubleSource'),
),
Stack(
children: <Widget>[
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const IgnorePointer(
child: SizedBox(
height: 100.0,
child: Text('Target1'),
),
);
},
onAccept: acceptedInts.add,
onAcceptWithDetails: acceptedIntsDetails.add,
),
DragTarget<double>(
builder: (BuildContext context, List<double?> data, List<dynamic> rejects) {
return const IgnorePointer(
child: SizedBox(
height: 100.0,
child: Text('Target2'),
),
);
},
onAccept: acceptedDoubles.add,
onAcceptWithDetails: acceptedDoublesDetails.add,
),
],
),
],
),
));
expect(acceptedInts, isEmpty);
expect(acceptedIntsDetails, isEmpty);
expect(acceptedDoubles, isEmpty);
expect(acceptedDoublesDetails, isEmpty);
expect(find.text('IntSource'), findsOneWidget);
expect(find.text('IntDragging'), findsNothing);
expect(find.text('DoubleSource'), findsOneWidget);
expect(find.text('DoubleDragging'), findsNothing);
expect(find.text('Target1'), findsOneWidget);
expect(find.text('Target2'), findsOneWidget);
final Offset intLocation = tester.getCenter(find.text('IntSource'));
final Offset doubleLocation = tester.getCenter(find.text('DoubleSource'));
final Offset targetLocation = tester.getCenter(find.text('Target1'));
// Drag the double draggable.
final TestGesture doubleGesture = await tester.startGesture(doubleLocation, pointer: 7);
await tester.pump();
expect(acceptedInts, isEmpty);
expect(acceptedIntsDetails, isEmpty);
expect(acceptedDoubles, isEmpty);
expect(acceptedDoublesDetails, isEmpty);
expect(find.text('IntDragging'), findsNothing);
expect(find.text('DoubleDragging'), findsOneWidget);
await doubleGesture.moveTo(targetLocation);
await tester.pump();
expect(acceptedInts, isEmpty);
expect(acceptedIntsDetails, isEmpty);
expect(acceptedDoubles, isEmpty);
expect(acceptedDoublesDetails, isEmpty);
expect(find.text('IntDragging'), findsNothing);
expect(find.text('DoubleDragging'), findsOneWidget);
await doubleGesture.up();
await tester.pump();
expect(acceptedInts, isEmpty);
expect(acceptedIntsDetails, isEmpty);
expect(acceptedDoubles, equals(<double>[1.0]));
expect(acceptedDoublesDetails, hasLength(1));
expect(acceptedDoublesDetails.first.offset, const Offset(112.0, 122.0));
expect(find.text('IntDragging'), findsNothing);
expect(find.text('DoubleDragging'), findsNothing);
acceptedDoubles.clear();
acceptedDoublesDetails.clear();
// Drag the int draggable.
final TestGesture intGesture = await tester.startGesture(intLocation, pointer: 7);
await tester.pump();
expect(acceptedInts, isEmpty);
expect(acceptedIntsDetails, isEmpty);
expect(acceptedDoubles, isEmpty);
expect(acceptedDoublesDetails, isEmpty);
expect(find.text('IntDragging'), findsOneWidget);
expect(find.text('DoubleDragging'), findsNothing);
await intGesture.moveTo(targetLocation);
await tester.pump();
expect(acceptedInts, isEmpty);
expect(acceptedIntsDetails, isEmpty);
expect(acceptedDoubles, isEmpty);
expect(acceptedDoublesDetails, isEmpty);
expect(find.text('IntDragging'), findsOneWidget);
expect(find.text('DoubleDragging'), findsNothing);
await intGesture.up();
await tester.pump();
expect(acceptedInts, equals(<int>[1]));
expect(acceptedIntsDetails, hasLength(1));
expect(acceptedIntsDetails.first.offset, const Offset(184.0, 122.0));
expect(acceptedDoubles, isEmpty);
expect(acceptedDoublesDetails, isEmpty);
expect(find.text('IntDragging'), findsNothing);
expect(find.text('DoubleDragging'), findsNothing);
});
testWidgets('Drag and drop - allow pass through of unaccepted data twice test', (WidgetTester tester) async {
final List<DragTargetData> acceptedDragTargetDatas = <DragTargetData>[];
final List<DragTargetDetails<DragTargetData>> acceptedDragTargetDataDetails = <DragTargetDetails<DragTargetData>>[];
final List<ExtendedDragTargetData> acceptedExtendedDragTargetDatas = <ExtendedDragTargetData>[];
final List<DragTargetDetails<ExtendedDragTargetData>> acceptedExtendedDragTargetDataDetails = <DragTargetDetails<ExtendedDragTargetData>>[];
final DragTargetData dragTargetData = DragTargetData();
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<DragTargetData>(
data: dragTargetData,
feedback: const Text('Dragging'),
child: const Text('Source'),
),
Stack(
children: <Widget>[
DragTarget<DragTargetData>(
builder: (BuildContext context, List<DragTargetData?> data, List<dynamic> rejects) {
return const IgnorePointer(
child: SizedBox(
height: 100.0,
child: Text('Target1'),
),
);
}, onAccept: acceptedDragTargetDatas.add,
onAcceptWithDetails: acceptedDragTargetDataDetails.add,
),
DragTarget<ExtendedDragTargetData>(
builder: (BuildContext context, List<ExtendedDragTargetData?> data, List<dynamic> rejects) {
return const IgnorePointer(
child: SizedBox(
height: 100.0,
child: Text('Target2'),
),
);
},
onAccept: acceptedExtendedDragTargetDatas.add,
onAcceptWithDetails: acceptedExtendedDragTargetDataDetails.add,
),
],
),
],
),
));
final Offset dragTargetLocation = tester.getCenter(find.text('Source'));
final Offset targetLocation = tester.getCenter(find.text('Target1'));
for (int i = 0; i < 2; i += 1) {
final TestGesture gesture = await tester.startGesture(dragTargetLocation);
await tester.pump();
await gesture.moveTo(targetLocation);
await tester.pump();
await gesture.up();
await tester.pump();
expect(acceptedDragTargetDatas, equals(<DragTargetData>[dragTargetData]));
expect(acceptedDragTargetDataDetails, hasLength(1));
expect(acceptedDragTargetDataDetails.first.offset, const Offset(256.0, 74.0));
expect(acceptedExtendedDragTargetDatas, isEmpty);
expect(acceptedExtendedDragTargetDataDetails, isEmpty);
acceptedDragTargetDatas.clear();
acceptedDragTargetDataDetails.clear();
await tester.pump();
}
});
testWidgets('Drag and drop - maxSimultaneousDrags', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
Widget build(int maxSimultaneousDrags) {
return MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
maxSimultaneousDrags: maxSimultaneousDrags,
feedback: const Text('Dragging'),
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
);
}
await tester.pumpWidget(build(0));
final Offset firstLocation = tester.getCenter(find.text('Source'));
final Offset secondLocation = tester.getCenter(find.text('Target'));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
await gesture.up();
await tester.pumpWidget(build(2));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
final TestGesture gesture1 = await tester.startGesture(firstLocation, pointer: 8);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
final TestGesture gesture2 = await tester.startGesture(firstLocation, pointer: 9);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNWidgets(2));
expect(find.text('Target'), findsOneWidget);
final TestGesture gesture3 = await tester.startGesture(firstLocation, pointer: 10);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNWidgets(2));
expect(find.text('Target'), findsOneWidget);
await gesture1.moveTo(secondLocation);
await gesture2.moveTo(secondLocation);
await gesture3.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNWidgets(2));
expect(find.text('Target'), findsOneWidget);
await gesture1.up();
await tester.pump();
expect(accepted, equals(<int>[1]));
expect(acceptedDetails, hasLength(1));
expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
await gesture2.up();
await tester.pump();
expect(accepted, equals(<int>[1, 1]));
expect(acceptedDetails, hasLength(2));
expect(acceptedDetails[0].offset, const Offset(256.0, 74.0));
expect(acceptedDetails[1].offset, const Offset(256.0, 74.0));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
await gesture3.up();
await tester.pump();
expect(accepted, equals(<int>[1, 1]));
expect(acceptedDetails, hasLength(2));
expect(acceptedDetails[0].offset, const Offset(256.0, 74.0));
expect(acceptedDetails[1].offset, const Offset(256.0, 74.0));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
});
testWidgets('Draggable disposes recognizer', (WidgetTester tester) async {
bool didTap = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) => GestureDetector(
onTap: () {
didTap = true;
},
child: Draggable<Object>(
feedback: Container(
width: 100.0,
height: 100.0,
color: const Color(0xFFFF0000),
),
child: Container(
color: const Color(0xFFFFFF00),
),
),
),
),
],
),
),
);
await tester.startGesture(const Offset(10.0, 10.0));
expect(didTap, isFalse);
// This tears down the draggable without terminating the gesture sequence,
// which used to trigger asserts in the multi-drag gesture recognizer.
await tester.pumpWidget(Container(key: UniqueKey()));
expect(didTap, isFalse);
});
// Regression test for https://github.com/flutter/flutter/issues/6128.
testWidgets('Draggable plays nice with onTap', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
OverlayEntry(
builder: (BuildContext context) => GestureDetector(
onTap: () { /* registers a tap recognizer */ },
child: Draggable<Object>(
feedback: Container(
width: 100.0,
height: 100.0,
color: const Color(0xFFFF0000),
),
child: Container(
color: const Color(0xFFFFFF00),
),
),
),
),
],
),
),
);
final TestGesture firstGesture = await tester.startGesture(const Offset(10.0, 10.0), pointer: 24);
final TestGesture secondGesture = await tester.startGesture(const Offset(10.0, 20.0), pointer: 25);
await firstGesture.moveBy(const Offset(100.0, 0.0));
await secondGesture.up();
});
testWidgets('DragTarget does not set state when remove from the tree', (WidgetTester tester) async {
final List<String> events = <String>[];
Offset firstLocation, secondLocation;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const Text('Target');
},
onAccept: (int? data) {
events.add('drop');
},
onAcceptWithDetails: (DragTargetDetails<int> _) {
events.add('details');
},
),
],
),
));
expect(events, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(events, isEmpty);
await tester.tap(find.text('Source'));
expect(events, isEmpty);
firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
await tester.pump(const Duration(seconds: 20));
secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
await tester.pumpWidget(const MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
],
),
));
expect(events, isEmpty);
await gesture.up();
await tester.pump();
});
testWidgets('Drag and drop - remove draggable', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
child: Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsNothing);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsNothing);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
await gesture.up();
await tester.pump();
expect(accepted, equals(<int>[1]));
expect(acceptedDetails, hasLength(1));
expect(acceptedDetails.first.offset, const Offset(256.0, 26.0));
expect(find.text('Source'), findsNothing);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
});
testWidgets('Tap above long-press draggable works', (WidgetTester tester) async {
final List<String> events = <String>[];
await tester.pumpWidget(MaterialApp(
home: Material(
child: Center(
child: GestureDetector(
onTap: () {
events.add('tap');
},
child: const LongPressDraggable<int>(
feedback: Text('Feedback'),
child: Text('X'),
),
),
),
),
));
expect(events, isEmpty);
await tester.tap(find.text('X'));
expect(events, equals(<String>['tap']));
});
testWidgets('long-press draggable calls onDragEnd called if dropped on accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDragEndCalled = false;
late DraggableDetails onDragEndDraggableDetails;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
LongPressDraggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragEnd: (DraggableDetails details) {
onDragEndCalled = true;
onDragEndDraggableDetails = details;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
await tester.pump(kLongPressTimeout);
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
await gesture.up();
await tester.pump();
final Offset droppedLocation = tester.getTopLeft(find.text('Target'));
final Offset expectedDropOffset = Offset(droppedLocation.dx, secondLocation.dy - firstLocation.dy);
expect(accepted, equals(<int>[1]));
expect(acceptedDetails, hasLength(1));
expect(acceptedDetails.first.offset, expectedDropOffset);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isTrue);
expect(onDragEndDraggableDetails, isNotNull);
expect(onDragEndDraggableDetails.wasAccepted, isTrue);
expect(onDragEndDraggableDetails.velocity, equals(Velocity.zero));
expect(onDragEndDraggableDetails.offset, equals(expectedDropOffset));
});
testWidgets('long-press draggable calls onDragCompleted called if dropped on accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDragCompletedCalled = false;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
LongPressDraggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragCompleted: () {
onDragCompletedCalled = true;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
await tester.pump(kLongPressTimeout);
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
await gesture.up();
await tester.pump();
expect(accepted, equals(<int>[1]));
expect(acceptedDetails.first.offset, const Offset(256.0, 74.0));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isTrue);
});
testWidgets('long-press draggable calls onDragStartedCalled after long press', (WidgetTester tester) async {
bool onDragStartedCalled = false;
await tester.pumpWidget(MaterialApp(
home: LongPressDraggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragStarted: () {
onDragStartedCalled = true;
},
child: const Text('Source'),
),
));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(onDragStartedCalled, isFalse);
final Offset firstLocation = tester.getCenter(find.text('Source'));
await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(onDragStartedCalled, isFalse);
await tester.pump(kLongPressTimeout);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(onDragStartedCalled, isTrue);
});
testWidgets('Custom long press delay for LongPressDraggable', (WidgetTester tester) async {
bool onDragStartedCalled = false;
await tester.pumpWidget(MaterialApp(
home: LongPressDraggable<int>(
data: 1,
delay: const Duration(seconds: 2),
feedback: const Text('Dragging'),
onDragStarted: () {
onDragStartedCalled = true;
},
child: const Text('Source'),
),
));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(onDragStartedCalled, isFalse);
final Offset firstLocation = tester.getCenter(find.text('Source'));
await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(onDragStartedCalled, isFalse);
// Halfway into the long press duration.
await tester.pump(const Duration(seconds: 1));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(onDragStartedCalled, isFalse);
// Long press draggable should be showing.
await tester.pump(const Duration(seconds: 1));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(onDragStartedCalled, isTrue);
});
testWidgets('Default long press delay for LongPressDraggable', (WidgetTester tester) async {
bool onDragStartedCalled = false;
await tester.pumpWidget(MaterialApp(
home: LongPressDraggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragStarted: () {
onDragStartedCalled = true;
},
child: const Text('Source'),
),
));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(onDragStartedCalled, isFalse);
final Offset firstLocation = tester.getCenter(find.text('Source'));
await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(onDragStartedCalled, isFalse);
// Halfway into the long press duration.
await tester.pump(const Duration(milliseconds: 250));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(onDragStartedCalled, isFalse);
// Long press draggable should be showing.
await tester.pump(const Duration(milliseconds: 250));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(onDragStartedCalled, isTrue);
});
testWidgets('long-press draggable calls Haptic Feedback onStart', (WidgetTester tester) async {
await _testLongPressDraggableHapticFeedback(tester: tester, hapticFeedbackOnStart: true, expectedHapticFeedbackCount: 1);
});
testWidgets('long-press draggable can disable Haptic Feedback', (WidgetTester tester) async {
await _testLongPressDraggableHapticFeedback(tester: tester, hapticFeedbackOnStart: false, expectedHapticFeedbackCount: 0);
});
testWidgets('Drag feedback with child anchor positions correctly', (WidgetTester tester) async {
await _testChildAnchorFeedbackPosition(tester: tester);
});
testWidgets('Drag feedback with child anchor within a non-global Overlay positions correctly', (WidgetTester tester) async {
await _testChildAnchorFeedbackPosition(tester: tester, left: 100.0, top: 100.0);
});
testWidgets('Drag feedback is put on root overlay with [rootOverlay] flag', (WidgetTester tester) async {
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> childNavigatorKey = GlobalKey<NavigatorState>();
// Create a [MaterialApp], with a nested [Navigator], which has the
// [Draggable].
await tester.pumpWidget(MaterialApp(
navigatorKey: rootNavigatorKey,
home: Column(
children: <Widget>[
SizedBox(
height: 200.0,
child: Navigator(
key: childNavigatorKey,
onGenerateRoute: (RouteSettings settings) {
if (settings.name == '/') {
return MaterialPageRoute<void>(
settings: settings,
builder: (BuildContext context) => const Draggable<int>(
data: 1,
feedback: Text('Dragging'),
rootOverlay: true,
child: Text('Source'),
),
);
}
throw UnsupportedError('Unsupported route: $settings');
},
),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(
height: 300.0, child: Center(child: Text('Target 1')),
);
},
),
],
),
));
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture =
await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
final Offset secondLocation = tester.getCenter(find.text('Target 1'));
await gesture.moveTo(secondLocation);
await tester.pump();
// Expect that the feedback widget is a descendant of the root overlay,
// but not a descendant of the child overlay.
expect(
find.descendant(
of: find.byType(Overlay).first,
matching: find.text('Dragging'),
),
findsOneWidget,
);
expect(
find.descendant(
of: find.byType(Overlay).last,
matching: find.text('Dragging'),
),
findsNothing,
);
});
// Regression test for https://github.com/flutter/flutter/issues/72483
testWidgets('Drag and drop - DragTarget<Object> can accept Draggable<int> data', (WidgetTester tester) async {
final List<Object> accepted = <Object