blob: 66488fd7b9d31f50a2afb867413728849336c368 [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.
import 'package:flutter/semantics.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:flutter/gestures.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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragStarted: () {
++dragStartedCount;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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);
});
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,
child: Text('Source'),
feedback: Text('Dragging'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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 Container(height: 100.0, child: const 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));
});
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,
child: Text('Source'),
feedback: Text('Dragging'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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 Container(height: 100.0, child: const 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,
child: Text('Source'),
feedback: Text('Dragging'),
),
Stack(
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
events.add('tap');
},
child: Container(
child: const Text('Button'),
),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return IgnorePointer(
child: Container(child: 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('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'));
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'));
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,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
events.add('tap');
},
child: Container(child: const Text('Button')),
),
feedback: const Text('Dragging'),
),
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,
child: Text('Source'),
feedback: Text('Dragging'),
),
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,
child: Text('Source'),
feedback: Text('Dragging'),
),
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,
child: Text('H'),
feedback: Text('Dragging'),
affinity: Axis.horizontal,
),
const Draggable<int>(
data: 2,
child: Text('V'),
feedback: Text('Dragging'),
affinity: Axis.vertical,
),
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,
child: Text('H'),
feedback: Text('Dragging'),
affinity: Axis.horizontal,
),
const Draggable<int>(
data: 2,
child: Text('V'),
feedback: Text('Dragging'),
affinity: Axis.vertical,
),
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,
child: Text('H'),
feedback: Text('H'),
childWhenDragging: SizedBox(),
axis: Axis.horizontal,
),
const Draggable<int>(
data: 2,
child: Text('V'),
feedback: Text('V'),
childWhenDragging: SizedBox(),
axis: Axis.vertical,
),
const Draggable<int>(
data: 3,
child: Text('N'),
feedback: Text('N'),
childWhenDragging: SizedBox(),
),
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);
});
});
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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDraggableCanceled: (Velocity velocity, Offset offset) {
onDraggableCanceledCalled = true;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDraggableCanceled: (Velocity velocity, Offset offset) {
onDraggableCanceledCalled = true;
onDraggableCanceledVelocity = velocity;
onDraggableCanceledOffset = offset;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(
height: 100.0,
child: const 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,
child: const Text('Source'),
feedback: const Text('Source'),
onDraggableCanceled: (Velocity velocity, Offset offset) {
onDraggableCanceledCalled = true;
onDraggableCanceledVelocity = velocity;
onDraggableCanceledOffset = offset;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(
height: 100.0,
child: const 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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragEnd: (DraggableDetails details) {
onDragEndCalled = true;
onDragEndDraggableDetails = details;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(
height: 100.0,
child: const 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,
child: Text('Source'),
feedback: Text('Dragging'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(
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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDraggableCanceled: (Velocity velocity, Offset offset) {
numberOfTimesOnDraggableCanceledCalled++;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(
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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragCompleted: () {
onDragCompletedCalled = true;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(
height: 100.0,
child: const 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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragEnd: (DraggableDetails details) {
onDragEndCalled = true;
onDragEndDraggableDetails = details;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragEnd: (DraggableDetails details) {
timesOnDragEndCalled++;
},
),
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(MaterialApp(
home: Column(
children: const <Widget>[
Draggable<int>(
data: 1,
child: Text('Source'),
feedback: Text('Dragging'),
),
],
),
));
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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragCompleted: () {
onDragCompletedCalled = true;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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 thru 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,
child: Text('IntSource'),
feedback: Text('IntDragging'),
),
const Draggable<double>(
data: 1.0,
child: Text('DoubleSource'),
feedback: Text('DoubleDragging'),
),
Stack(
children: <Widget>[
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return IgnorePointer(
child: Container(
height: 100.0,
child: const Text('Target1'),
),
);
},
onAccept: acceptedInts.add,
onAcceptWithDetails: acceptedIntsDetails.add,
),
DragTarget<double>(
builder: (BuildContext context, List<double?> data, List<dynamic> rejects) {
return IgnorePointer(
child: Container(
height: 100.0,
child: const 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 thru 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,
child: const Text('Source'),
feedback: const Text('Dragging'),
),
Stack(
children: <Widget>[
DragTarget<DragTargetData>(
builder: (BuildContext context, List<DragTargetData?> data, List<dynamic> rejects) {
return IgnorePointer(
child: Container(
height: 100.0,
child: const Text('Target1'),
),
);
}, onAccept: acceptedDragTargetDatas.add,
onAcceptWithDetails: acceptedDragTargetDataDetails.add,
),
DragTarget<ExtendedDragTargetData>(
builder: (BuildContext context, List<ExtendedDragTargetData?> data, List<dynamic> rejects) {
return IgnorePointer(
child: Container(
height: 100.0,
child: const 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,
child: const Text('Source'),
feedback: const Text('Dragging'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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>(
child: Container(
color: const Color(0xFFFFFF00),
),
feedback: Container(
width: 100.0,
height: 100.0,
color: const Color(0xFFFF0000),
),
),
),
),
],
),
),
);
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>(
child: Container(
color: const Color(0xFFFFFF00),
),
feedback: Container(
width: 100.0,
height: 100.0,
color: const Color(0xFFFF0000),
),
),
),
),
],
),
),
);
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,
child: Text('Source'),
feedback: Text('Dragging'),
),
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(MaterialApp(
home: Column(
children: const <Widget>[
Draggable<int>(
data: 1,
child: Text('Source'),
feedback: Text('Dragging'),
),
],
),
));
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,
child: Text('Source'),
feedback: Text('Dragging'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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 Container(height: 100.0, child: const 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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragEnd: (DraggableDetails details) {
onDragEndCalled = true;
onDragEndDraggableDetails = details;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragCompleted: () {
onDragCompletedCalled = true;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragStarted: () {
onDragStartedCalled = true;
},
),
));
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('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>[
Container(
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,
child: Text('Source'),
feedback: Text('Dragging'),
rootOverlay: true,
),
);
}
throw UnsupportedError('Unsupported route: $settings');
},
),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(
height: 300.0, child: const 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);
});
testWidgets('Drag and drop can contribute semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(MaterialApp(
home: ListView(
scrollDirection: Axis.horizontal,
addSemanticIndexes: false,
children: <Widget>[
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const Text('Target');
},
),
Container(width: 400.0),
const Draggable<int>(
data: 1,
child: Text('H'),
feedback: Text('H'),
childWhenDragging: SizedBox(),
axis: Axis.horizontal,
ignoringFeedbackSemantics: false,
),
const Draggable<int>(
data: 2,
child: Text('V'),
feedback: Text('V'),
childWhenDragging: SizedBox(),
axis: Axis.vertical,
ignoringFeedbackSemantics: false,
),
const Draggable<int>(
data: 3,
child: Text('N'),
feedback: Text('N'),
childWhenDragging: SizedBox(),
),
],
),
));
expect(semantics, hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
id: 2,
children: <TestSemantics>[
TestSemantics(
id: 3,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 4,
children: <TestSemantics>[
TestSemantics(
id: 9,
flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
actions: <SemanticsAction>[SemanticsAction.scrollLeft],
children: <TestSemantics>[
TestSemantics(
id: 5,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
label: 'Target',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 6,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
label: 'H',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 7,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
label: 'V',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 8,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
label: 'N',
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
],
),
],
), ignoreTransform: true, ignoreRect: true));
final Offset firstLocation = tester.getTopLeft(find.text('N'));
final Offset secondLocation = 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(semantics, hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics(
id: 1,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
TestSemantics(
id: 2,
children: <TestSemantics>[
TestSemantics(
id: 3,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
TestSemantics(
id: 4,
children: <TestSemantics>[
TestSemantics(
id: 9,
flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
children: <TestSemantics>[
TestSemantics(
id: 5,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
label: 'Target',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 6,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
label: 'H',
textDirection: TextDirection.ltr,
),
TestSemantics(
id: 7,
tags: <SemanticsTag>[const SemanticsTag('RenderViewport.twoPane')],
label: 'V',
textDirection: TextDirection.ltr,
),
/// N is moved offscreen.
],
),
],
),
],
),
],
),
],
),
],
), ignoreTransform: true, ignoreRect: true));
semantics.dispose();
});
}
Future<void> _testLongPressDraggableHapticFeedback({ required WidgetTester tester, required bool hapticFeedbackOnStart, required int expectedHapticFeedbackCount }) async {
bool onDragStartedCalled = false;
int hapticFeedbackCalls = 0;
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
if (methodCall.method == 'HapticFeedback.vibrate') {
hapticFeedbackCalls++;
}
});
await tester.pumpWidget(MaterialApp(
home: LongPressDraggable<int>(
data: 1,
child: const Text('Source'),
feedback: const Text('Dragging'),
hapticFeedbackOnStart: hapticFeedbackOnStart,
onDragStarted: () {
onDragStartedCalled = true;
},
),
));
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);
expect(hapticFeedbackCalls, expectedHapticFeedbackCount);
}
Future<void> _testChildAnchorFeedbackPosition({ required WidgetTester tester, double top = 0.0, double left = 0.0 }) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
int dragStartedCount = 0;
await tester.pumpWidget(
Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
Positioned(
left: left,
top: top,
right: 0.0,
bottom: 0.0,
child: MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragStarted: () {
++dragStartedCount;
},
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return Container(height: 100.0, child: const 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(dragStartedCount, 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);
final Offset secondLocation = tester.getBottomRight(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);
final Offset feedbackTopLeft = tester.getTopLeft(find.text('Dragging'));
final Offset sourceTopLeft = tester.getTopLeft(find.text('Source'));
final Offset dragOffset = secondLocation - firstLocation;
expect(feedbackTopLeft, equals(sourceTopLeft + dragOffset));
}
class DragTargetData { }
class ExtendedDragTargetData extends DragTargetData { }