Kate Lovett 9d96df2364
Modernize framework lints (#179089)
WIP

Commits separated as follows:
- Update lints in analysis_options files
- Run `dart fix --apply`
- Clean up leftover analysis issues 
- Run `dart format .` in the right places.

Local analysis and testing passes. Checking CI now.

Part of https://github.com/flutter/flutter/issues/178827
- Adoption of flutter_lints in examples/api coming in a separate change
(cc @loic-sharma)

## Pre-launch Checklist

- [ ] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [ ] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [ ] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [ ] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [ ] I updated/added relevant documentation (doc comments with `///`).
- [ ] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [ ] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-11-26 01:10:39 +00:00

4078 lines
139 KiB
Dart

// 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 accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var dragStartedCount = 0;
var 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 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 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 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 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 - onMove is not called if moved with null data', (
WidgetTester tester,
) async {
var onMoveCalled = false;
await tester.pumpWidget(
MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(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'));
},
onMove: (DragTargetDetails<dynamic> details) {
onMoveCalled = true;
},
),
],
),
),
);
expect(onMoveCalled, isFalse);
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(onMoveCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(onMoveCalled, isFalse);
await gesture.up();
await tester.pump();
expect(onMoveCalled, isFalse);
});
testWidgets('Drag and drop - dragging over button', (WidgetTester tester) async {
final 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 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 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 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 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 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 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', () {
var 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 accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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 accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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 details',
(WidgetTester tester) async {
final accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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'));
},
onWillAcceptWithDetails: (DragTargetDetails<int> details) => 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 accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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 accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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 - onDragEnd not called if dropped on non-accepting target with details',
(WidgetTester tester) async {
final accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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'));
},
onWillAcceptWithDetails: (DragTargetDetails<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 {
var 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 accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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 - onDragCompleted not called if dropped on non-accepting target with details',
(WidgetTester tester) async {
final accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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'));
},
onWillAcceptWithDetails: (DragTargetDetails<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 accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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 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 events = <String>[];
Offset firstLocation, secondLocation;
var 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 accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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 acceptedInts = <int>[];
final acceptedIntsDetails = <DragTargetDetails<int>>[];
final acceptedDoubles = <double>[];
final 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 acceptedDragTargetDatas = <DragTargetData>[];
final acceptedDragTargetDataDetails = <DragTargetDetails<DragTargetData>>[];
final acceptedExtendedDragTargetDatas = <ExtendedDragTargetData>[];
final acceptedExtendedDragTargetDataDetails = <DragTargetDetails<ExtendedDragTargetData>>[];
final 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 (var 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 accepted = <int>[];
final 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('Drag and drop - onAccept is not called if dropped with null data', (
WidgetTester tester,
) async {
var onAcceptCalled = false;
var onAcceptWithDetailsCalled = false;
await tester.pumpWidget(
MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(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: (int data) {
onAcceptCalled = true;
},
onAcceptWithDetails: (DragTargetDetails<int> details) {
onAcceptWithDetailsCalled = true;
},
),
],
),
),
);
expect(onAcceptCalled, isFalse);
expect(onAcceptWithDetailsCalled, isFalse);
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(onAcceptCalled, isFalse);
expect(onAcceptWithDetailsCalled, isFalse);
expect(find.text('Source'), findsOneWidget);
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(onAcceptCalled, isFalse);
expect(onAcceptWithDetailsCalled, isFalse);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
await gesture.up();
await tester.pump();
expect(onAcceptCalled, isFalse, reason: 'onAccept should not be called when data is null');
expect(
onAcceptWithDetailsCalled,
isFalse,
reason: 'onAcceptWithDetails should not be called when data is null',
);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
});
testWidgets('Draggable disposes recognizer', (WidgetTester tester) async {
late final OverlayEntry entry;
addTearDown(
() => entry
..remove()
..dispose(),
);
var didTap = false;
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
entry = 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)),
),
),
),
],
),
),
);
final TestGesture gesture = 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);
// Finish gesture to release resources.
await gesture.up();
await tester.pumpAndSettle();
});
// Regression test for https://github.com/flutter/flutter/issues/6128.
testWidgets('Draggable plays nice with onTap', (WidgetTester tester) async {
late final OverlayEntry entry;
addTearDown(
() => entry
..remove()
..dispose(),
);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Overlay(
initialEntries: <OverlayEntry>[
entry = 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();
await firstGesture.up();
});
testWidgets('DragTarget does not set state when remove from the tree', (
WidgetTester tester,
) async {
final 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 accepted = <int>[];
final 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 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 accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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 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 accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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 {
var 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'));
final TestGesture gesture = 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);
// Finish gesture to release resources.
await gesture.up();
await tester.pumpAndSettle();
});
testWidgets('Custom long press delay for LongPressDraggable', (WidgetTester tester) async {
var 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'));
final TestGesture gesture = 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);
// Finish gesture to release resources.
await gesture.up();
await tester.pumpAndSettle();
});
testWidgets('Default long press delay for LongPressDraggable', (WidgetTester tester) async {
var 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'));
final TestGesture gesture = 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);
// Finish gesture to release resources.
await gesture.up();
await tester.pumpAndSettle();
});
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 rootNavigatorKey = GlobalKey<NavigatorState>();
final 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,
);
});
testWidgets('Drag feedback is put on root overlay with [rootOverlay] flag', (
WidgetTester tester,
) async {
final rootNavigatorKey = GlobalKey<NavigatorState>();
final 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 LongPressDraggable<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(kLongPressTimeout);
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('configurable DragTarget hit test behavior', (WidgetTester tester) async {
const HitTestBehavior hitTestBehavior = HitTestBehavior.opaque;
await tester.pumpWidget(
const MaterialApp(
home: Column(
children: <Widget>[
LongPressDraggable<int>(
hitTestBehavior: hitTestBehavior,
feedback: SizedBox(),
child: SizedBox(),
),
],
),
),
);
expect(
tester
.widget<Listener>(
find.descendant(of: find.byType(Column), matching: find.byType(Listener)),
)
.behavior,
hitTestBehavior,
);
});
// 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 accepted = <Object>[];
await tester.pumpWidget(
MaterialApp(
home: Column(
children: <Widget>[
const Draggable<int>(data: 1, feedback: Text('Dragging'), child: Text('Source')),
DragTarget<Object>(
builder: (BuildContext context, List<Object?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onAccept: accepted.add,
),
],
),
),
);
expect(accepted, isEmpty);
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'));
await gesture.moveTo(secondLocation);
await tester.pump();
await gesture.up();
await tester.pump();
expect(accepted, equals(<int>[1]));
});
testWidgets(
'Drag and drop - DragTarget<int> can accept Draggable<Object> data when runtime type is int',
(WidgetTester tester) async {
final accepted = <int>[];
await tester.pumpWidget(
MaterialApp(
home: Column(
children: <Widget>[
const Draggable<Object>(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,
),
],
),
),
);
expect(accepted, isEmpty);
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'));
await gesture.moveTo(secondLocation);
await tester.pump();
await gesture.up();
await tester.pump();
expect(accepted, equals(<int>[1]));
},
);
testWidgets(
'Drag and drop - DragTarget<int> should not accept Draggable<Object> data when runtime type null',
(WidgetTester tester) async {
final accepted = <int>[];
var isReceiveNullDataForCheck = false;
await tester.pumpWidget(
MaterialApp(
home: Column(
children: <Widget>[
const Draggable<Object>(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,
onWillAccept: (int? data) {
if (data == null) {
isReceiveNullDataForCheck = true;
}
return data != null;
},
),
],
),
),
);
expect(accepted, isEmpty);
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'));
await gesture.moveTo(secondLocation);
await tester.pump();
await gesture.up();
await tester.pump();
expect(accepted, isEmpty);
expect(isReceiveNullDataForCheck, true);
},
);
testWidgets('Drag and drop can contribute semantics', (WidgetTester tester) async {
final 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,
feedback: Text('H'),
childWhenDragging: SizedBox(),
axis: Axis.horizontal,
ignoringFeedbackSemantics: false,
child: Text('H'),
),
const Draggable<int>(
data: 2,
feedback: Text('V'),
childWhenDragging: SizedBox(),
axis: Axis.vertical,
ignoringFeedbackSemantics: false,
child: Text('V'),
),
const Draggable<int>(
data: 3,
feedback: Text('N'),
childWhenDragging: SizedBox(),
child: Text('N'),
),
],
),
),
);
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,
SemanticsAction.scrollToOffset,
],
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,
actions: <SemanticsAction>[SemanticsAction.scrollToOffset],
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();
});
testWidgets('Drag and drop - when a dragAnchorStrategy is provided it gets called', (
WidgetTester tester,
) async {
var dragAnchorStrategyCalled = false;
await tester.pumpWidget(
MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
feedback: const Text('Feedback'),
dragAnchorStrategy:
(Draggable<Object> widget, BuildContext context, Offset position) {
dragAnchorStrategyCalled = true;
return Offset.zero;
},
child: const Text('Source'),
),
],
),
),
);
final Offset location = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(location, pointer: 7);
expect(dragAnchorStrategyCalled, true);
// Finish gesture to release resources.
await gesture.up();
await tester.pumpAndSettle();
});
testWidgets('Drag and drop - feedback matches pointer in scaled MaterialApp', (
WidgetTester tester,
) async {
await tester.pumpWidget(
Transform.scale(
scale: 0.5,
child: const MaterialApp(
home: Scaffold(
body: Draggable<int>(data: 42, feedback: Text('Feedback'), child: Text('Source')),
),
),
),
);
final Offset location = tester.getTopLeft(find.text('Source'));
final TestGesture gesture = await tester.startGesture(location);
final Offset secondLocation = location + const Offset(100, 100);
await gesture.moveTo(secondLocation);
await tester.pump();
final Offset appTopLeft = tester.getTopLeft(find.byType(MaterialApp));
expect(tester.getTopLeft(find.text('Source')), appTopLeft);
expect(tester.getTopLeft(find.text('Feedback')), secondLocation);
// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});
testWidgets('Drag and drop - childDragAnchorStrategy works in scaled MaterialApp', (
WidgetTester tester,
) async {
final Key sourceKey = UniqueKey();
final Key feedbackKey = UniqueKey();
await tester.pumpWidget(
Transform.scale(
scale: 0.5,
child: MaterialApp(
home: Scaffold(
body: Draggable<int>(
data: 42,
feedback: Text('Text', key: feedbackKey),
child: Text('Text', key: sourceKey),
),
),
),
),
);
final Finder source = find.byKey(sourceKey);
final Finder feedback = find.byKey(feedbackKey);
final TestGesture gesture = await tester.startGesture(tester.getCenter(source));
await tester.pump();
expect(tester.getTopLeft(source), tester.getTopLeft(feedback));
// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});
testWidgets('Drag and drop - feedback matches pointer in rotated MaterialApp', (
WidgetTester tester,
) async {
await tester.pumpWidget(
Transform.rotate(
angle: 1, // ~57 degrees
child: const MaterialApp(
home: Scaffold(
body: Draggable<int>(data: 42, feedback: Text('Feedback'), child: Text('Source')),
),
),
),
);
final Offset location = tester.getTopLeft(find.text('Source'));
final TestGesture gesture = await tester.startGesture(location);
final Offset secondLocation = location + const Offset(100, 100);
await gesture.moveTo(secondLocation);
await tester.pump();
final Offset appTopLeft = tester.getTopLeft(find.byType(MaterialApp));
expect(tester.getTopLeft(find.text('Source')), appTopLeft);
final Offset feedbackTopLeft = tester.getTopLeft(find.text('Feedback'));
// Different rotations can incur rounding errors, this makes it more robust
expect(feedbackTopLeft.dx, moreOrLessEquals(secondLocation.dx));
expect(feedbackTopLeft.dy, moreOrLessEquals(secondLocation.dy));
// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});
testWidgets('Drag and drop - unmounting overlay ends drag gracefully', (
WidgetTester tester,
) async {
final mountedNotifier = ValueNotifier<bool>(true);
addTearDown(mountedNotifier.dispose);
await tester.pumpWidget(
ValueListenableBuilder<bool>(
valueListenable: mountedNotifier,
builder: (_, bool value, _) => value
? const MaterialApp(
home: Scaffold(
body: Draggable<int>(data: 42, feedback: Text('Feedback'), child: Text('Source')),
),
)
: Container(),
),
);
final Offset location = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(location);
final Offset secondLocation = location + const Offset(100, 100);
await gesture.moveTo(secondLocation);
await tester.pump();
expect(find.text('Feedback'), findsOneWidget);
// Unmount overlay
mountedNotifier.value = false;
await tester.pump();
// This should not throw
await gesture.moveTo(location);
expect(find.byType(Container), findsOneWidget);
expect(find.text('Feedback'), findsNothing);
// Finish gesture to release resources.
await gesture.up();
await tester.pump();
});
testWidgets('configurable Draggable hit test behavior', (WidgetTester tester) async {
const HitTestBehavior hitTestBehavior = HitTestBehavior.deferToChild;
await tester.pumpWidget(
const MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
feedback: SizedBox(height: 50.0, child: Text('Draggable')),
child: SizedBox(height: 50.0, child: Text('Target')),
),
],
),
),
);
expect(tester.widget<Listener>(find.byType(Listener).first).behavior, hitTestBehavior);
});
// Regression test for https://github.com/flutter/flutter/issues/92083
testWidgets('feedback respect the MouseRegion cursor configure', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
ignoringFeedbackPointer: false,
feedback: MouseRegion(
cursor: SystemMouseCursors.grabbing,
child: SizedBox(height: 50.0, child: Text('Draggable')),
),
child: SizedBox(height: 50.0, child: Text('Target')),
),
],
),
),
);
final Offset location = tester.getCenter(find.text('Target'));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer(location: location);
await gesture.down(location);
await tester.pump();
expect(
RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1),
SystemMouseCursors.grabbing,
);
gesture.up();
});
testWidgets('configurable feedback ignore pointer behavior', (WidgetTester tester) async {
var onTap = false;
await tester.pumpWidget(
MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
ignoringFeedbackPointer: false,
feedback: GestureDetector(
onTap: () => onTap = true,
child: const SizedBox(height: 50.0, child: Text('Draggable')),
),
child: const SizedBox(height: 50.0, child: Text('Target')),
),
],
),
),
);
final Offset location = tester.getCenter(find.text('Target'));
final TestGesture gesture = await tester.startGesture(location, pointer: 7);
final Offset secondLocation = location + const Offset(7.0, 7.0);
await gesture.moveTo(secondLocation);
await tester.pump();
await tester.tap(find.text('Draggable'));
expect(onTap, true);
});
testWidgets('configurable feedback ignore pointer behavior - LongPressDraggable', (
WidgetTester tester,
) async {
var onTap = false;
await tester.pumpWidget(
MaterialApp(
home: Column(
children: <Widget>[
LongPressDraggable<int>(
ignoringFeedbackPointer: false,
feedback: GestureDetector(
onTap: () => onTap = true,
child: const SizedBox(height: 50.0, child: Text('Draggable')),
),
child: const SizedBox(height: 50.0, child: Text('Target')),
),
],
),
),
);
final Offset location = tester.getCenter(find.text('Target'));
final TestGesture gesture = await tester.startGesture(location, pointer: 7);
await tester.pump(kLongPressTimeout);
final Offset secondLocation = location + const Offset(7.0, 7.0);
await gesture.moveTo(secondLocation);
await tester.pump();
await tester.tap(find.text('Draggable'));
expect(onTap, true);
});
testWidgets('configurable DragTarget hit test behavior', (WidgetTester tester) async {
const HitTestBehavior hitTestBehavior = HitTestBehavior.deferToChild;
await tester.pumpWidget(
MaterialApp(
home: Column(
children: <Widget>[
DragTarget<int>(
hitTestBehavior: hitTestBehavior,
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
),
],
),
),
);
expect(tester.widget<MetaData>(find.byType(MetaData)).behavior, hitTestBehavior);
});
testWidgets('LongPressDraggable.dragAnchorStrategy', (WidgetTester tester) async {
const Widget widget1 = Placeholder(key: ValueKey<int>(1));
const Widget widget2 = Placeholder(key: ValueKey<int>(2));
Offset dummyStrategy(Draggable<Object> draggable, BuildContext context, Offset position) =>
Offset.zero;
expect(const LongPressDraggable<int>(feedback: widget2, child: widget1), isA<Draggable<int>>());
expect(const LongPressDraggable<int>(feedback: widget2, child: widget1).child, widget1);
expect(const LongPressDraggable<int>(feedback: widget2, child: widget1).feedback, widget2);
expect(
LongPressDraggable<int>(
feedback: widget2,
dragAnchorStrategy: dummyStrategy,
child: widget1,
).dragAnchorStrategy,
dummyStrategy,
);
});
testWidgets('Test allowedButtonsFilter', (WidgetTester tester) async {
Widget build(bool Function(int buttons)? allowedButtonsFilter) {
return MaterialApp(
home: Draggable<int>(
key: UniqueKey(),
allowedButtonsFilter: allowedButtonsFilter,
feedback: const Text('Dragging'),
child: const Text('Source'),
),
);
}
await tester.pumpWidget(build(null));
final Offset firstLocation = tester.getCenter(find.text('Source'));
expect(find.text('Dragging'), findsNothing);
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(find.text('Dragging'), findsOneWidget);
await gesture.up();
await tester.pumpWidget(build((int buttons) => buttons == kSecondaryButton));
expect(find.text('Dragging'), findsNothing);
final TestGesture gesture1 = await tester.startGesture(firstLocation, pointer: 8);
await tester.pump();
expect(find.text('Dragging'), findsNothing);
await gesture1.up();
await tester.pumpWidget(
build((int buttons) => buttons & kTertiaryButton != 0 || buttons & kPrimaryButton != 0),
);
expect(find.text('Dragging'), findsNothing);
final TestGesture gesture2 = await tester.startGesture(firstLocation, pointer: 8);
await tester.pump();
expect(find.text('Dragging'), findsOneWidget);
await gesture2.up();
await tester.pumpWidget(build((int buttons) => false));
expect(find.text('Dragging'), findsNothing);
final TestGesture gesture3 = await tester.startGesture(firstLocation, pointer: 8);
await tester.pump();
expect(find.text('Dragging'), findsNothing);
await gesture3.up();
});
testWidgets('throws error when both onWillAccept and onWillAcceptWithDetails are provided', (
WidgetTester tester,
) async {
expect(
() => DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onWillAccept: (int? data) => true,
onWillAcceptWithDetails: (DragTargetDetails<int> details) => false,
),
throwsAssertionError,
);
});
}
Future<void> _testLongPressDraggableHapticFeedback({
required WidgetTester tester,
required bool hapticFeedbackOnStart,
required int expectedHapticFeedbackCount,
}) async {
var onDragStartedCalled = false;
var hapticFeedbackCalls = 0;
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.platform, (
MethodCall methodCall,
) async {
if (methodCall.method == 'HapticFeedback.vibrate') {
hapticFeedbackCalls++;
}
return null;
});
await tester.pumpWidget(
MaterialApp(
home: LongPressDraggable<int>(
data: 1,
feedback: const Text('Dragging'),
hapticFeedbackOnStart: hapticFeedbackOnStart,
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'));
final TestGesture gesture = 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);
// Finish gesture to release resources.
await gesture.up();
await tester.pumpAndSettle();
}
Future<void> _testChildAnchorFeedbackPosition({
required WidgetTester tester,
double top = 0.0,
double left = 0.0,
}) async {
final accepted = <int>[];
final acceptedDetails = <DragTargetDetails<int>>[];
var 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,
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'));
},
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 {}