dismissible_test.dart 42.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Hixie's avatar
Hixie committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:async';

7
import 'package:flutter/gestures.dart' show DragStartBehavior;
8
import 'package:flutter/widgets.dart';
Ian Hickson's avatar
Ian Hickson committed
9
import 'package:flutter_test/flutter_test.dart';
10

11
const DismissDirection defaultDismissDirection = DismissDirection.horizontal;
12
const double crossAxisEndOffset = 0.5;
13 14
bool reportedDismissUpdateReached = false;
bool reportedDismissUpdatePreviousReached = false;
15
double reportedDismissUpdateProgress = 0.0;
16
late DismissDirection reportedDismissUpdateReachedDirection;
17

18 19 20
DismissDirection reportedDismissDirection = DismissDirection.horizontal;
List<int> dismissedItems = <int>[];

21
Widget buildTest({
22 23
  Axis scrollDirection = Axis.vertical,
  DismissDirection dismissDirection = defaultDismissDirection,
24
  double? startToEndThreshold,
25
  TextDirection textDirection = TextDirection.ltr,
26
  Future<bool?> Function(BuildContext context, DismissDirection direction)? confirmDismiss,
27
  ScrollController? controller,
28
  ScrollPhysics? scrollPhysics,
29
  Widget? background,
30
}) {
31
  return Directionality(
Ian Hickson's avatar
Ian Hickson committed
32
    textDirection: textDirection,
33
    child: StatefulBuilder(
Ian Hickson's avatar
Ian Hickson committed
34 35
      builder: (BuildContext context, StateSetter setState) {
        Widget buildDismissibleItem(int item) {
36
          return Dismissible(
37
            dragStartBehavior: DragStartBehavior.down,
38
            key: ValueKey<int>(item),
Ian Hickson's avatar
Ian Hickson committed
39
            direction: dismissDirection,
40 41 42
            confirmDismiss: confirmDismiss == null ? null : (DismissDirection direction) {
              return confirmDismiss(context, direction);
            },
Ian Hickson's avatar
Ian Hickson committed
43 44 45 46 47 48 49 50
            onDismissed: (DismissDirection direction) {
              setState(() {
                reportedDismissDirection = direction;
                expect(dismissedItems.contains(item), isFalse);
                dismissedItems.add(item);
              });
            },
            onResize: () {
51
              expect(dismissedItems.contains(item), isFalse);
Ian Hickson's avatar
Ian Hickson committed
52
            },
53 54 55 56
            onUpdate: (DismissUpdateDetails details) {
              reportedDismissUpdateReachedDirection = details.direction;
              reportedDismissUpdateReached = details.reached;
              reportedDismissUpdatePreviousReached = details.previousReached;
57
              reportedDismissUpdateProgress = details.progress;
58
            },
Ian Hickson's avatar
Ian Hickson committed
59 60 61 62
            background: background,
            dismissThresholds: startToEndThreshold == null
                ? <DismissDirection, double>{}
                : <DismissDirection, double>{DismissDirection.startToEnd: startToEndThreshold},
63
            crossAxisEndOffset: crossAxisEndOffset,
64
            child: SizedBox(
65 66
              width: 100.0,
              height: 100.0,
67
              child: Text(item.toString()),
Ian Hickson's avatar
Ian Hickson committed
68 69 70 71
            ),
          );
        }

72
        return Container(
Ian Hickson's avatar
Ian Hickson committed
73
          padding: const EdgeInsets.all(10.0),
74
          child: ListView(
75
            physics: scrollPhysics,
76
            controller: controller,
77
            dragStartBehavior: DragStartBehavior.down,
Ian Hickson's avatar
Ian Hickson committed
78
            scrollDirection: scrollDirection,
79
            itemExtent: 100.0,
80
            children: <int>[0, 1, 2, 3, 4, 5, 6, 7, 8]
Ian Hickson's avatar
Ian Hickson committed
81
              .where((int i) => !dismissedItems.contains(i))
82
              .map<Widget>(buildDismissibleItem).toList(),
83 84
          ),
        );
Ian Hickson's avatar
Ian Hickson committed
85 86
      },
    ),
87 88 89
  );
}

90
typedef DismissMethod = Future<void> Function(WidgetTester tester, Finder finder, { required AxisDirection gestureDirection });
91

92
Future<void> dismissElement(WidgetTester tester, Finder finder, { required AxisDirection gestureDirection }) async {
93 94
  Offset downLocation;
  Offset upLocation;
95
  switch (gestureDirection) {
Ian Hickson's avatar
Ian Hickson committed
96
    case AxisDirection.left:
97
      // getTopRight() returns a point that's just beyond itemWidget's right
98
      // edge and outside the Dismissible event listener's bounds.
99
      downLocation = tester.getTopRight(finder) + const Offset(-0.1, 0.0);
100
      upLocation = tester.getTopLeft(finder) + const Offset(-0.1, 0.0);
Ian Hickson's avatar
Ian Hickson committed
101
    case AxisDirection.right:
102
      // we do the same thing here to keep the test symmetric
103
      downLocation = tester.getTopLeft(finder) + const Offset(0.1, 0.0);
104
      upLocation = tester.getTopRight(finder) + const Offset(0.1, 0.0);
Ian Hickson's avatar
Ian Hickson committed
105
    case AxisDirection.up:
106
      // getBottomLeft() returns a point that's just below itemWidget's bottom
107
      // edge and outside the Dismissible event listener's bounds.
108
      downLocation = tester.getBottomLeft(finder) + const Offset(0.0, -0.1);
109
      upLocation = tester.getTopLeft(finder) + const Offset(0.0, -0.1);
Ian Hickson's avatar
Ian Hickson committed
110
    case AxisDirection.down:
111
      // again with doing the same here for symmetry
112
      downLocation = tester.getTopLeft(finder) + const Offset(0.1, 0.0);
113
      upLocation = tester.getBottomLeft(finder) + const Offset(0.1, 0.0);
114 115
  }

Ian Hickson's avatar
Ian Hickson committed
116
  final TestGesture gesture = await tester.startGesture(downLocation);
117 118
  await gesture.moveTo(upLocation);
  await gesture.up();
Hixie's avatar
Hixie committed
119 120
}

121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
Future<void> dragElement(WidgetTester tester, Finder finder, { required AxisDirection gestureDirection, required double amount }) async {
  Offset delta;
  switch (gestureDirection) {
    case AxisDirection.left:
      delta = Offset(-amount, 0.0);
    case AxisDirection.right:
      delta = Offset(amount, 0.0);
    case AxisDirection.up:
      delta = Offset(0.0, -amount);
    case AxisDirection.down:
      delta = Offset(0.0, amount);
  }
  await tester.drag(finder, delta);
}

136
Future<void> flingElement(WidgetTester tester, Finder finder, { required AxisDirection gestureDirection, double initialOffsetFactor = 0.0 }) async {
Ian Hickson's avatar
Ian Hickson committed
137 138 139 140 141 142 143 144 145 146 147 148 149
  Offset delta;
  switch (gestureDirection) {
    case AxisDirection.left:
      delta = const Offset(-300.0, 0.0);
    case AxisDirection.right:
      delta = const Offset(300.0, 0.0);
    case AxisDirection.up:
      delta = const Offset(0.0, -300.0);
    case AxisDirection.down:
      delta = const Offset(0.0, 300.0);
  }
  await tester.fling(finder, delta, 1000.0, initialOffset: delta * initialOffsetFactor);
}
Hixie's avatar
Hixie committed
150

151
Future<void> flingElementFromZero(WidgetTester tester, Finder finder, { required AxisDirection gestureDirection }) async {
Ian Hickson's avatar
Ian Hickson committed
152 153
  // This is a special case where we drag in one direction, then fling back so
  // that at the point of release, we're at exactly the point at which we
Josh Soref's avatar
Josh Soref committed
154
  // started, but with velocity. This is needed to check a boundary condition
Ian Hickson's avatar
Ian Hickson committed
155 156 157 158
  // in the flinging behavior.
  await flingElement(tester, finder, gestureDirection: gestureDirection, initialOffsetFactor: -1.0);
}

159 160 161
Future<void> dismissItem(
  WidgetTester tester,
  int item, {
162
  required AxisDirection gestureDirection,
163
  DismissMethod mechanism = dismissElement,
Ian Hickson's avatar
Ian Hickson committed
164
}) async {
165
  final Finder itemFinder = find.text(item.toString());
166
  expect(itemFinder, findsOneWidget);
Hixie's avatar
Hixie committed
167

Ian Hickson's avatar
Ian Hickson committed
168
  await mechanism(tester, itemFinder, gestureDirection: gestureDirection);
169
  await tester.pumpAndSettle();
170 171
}

172 173 174 175 176 177 178 179 180 181 182 183 184
Future<void> dragItem(
    WidgetTester tester,
    int item, {
      required AxisDirection gestureDirection,
      required double amount,
    }) async {
  final Finder itemFinder = find.text(item.toString());
  expect(itemFinder, findsOneWidget);

  await dragElement(tester, itemFinder, gestureDirection: gestureDirection, amount: amount);
  await tester.pump();
}

185 186 187
Future<void> checkFlingItemBeforeMovementEnd(
  WidgetTester tester,
  int item, {
188
  required AxisDirection gestureDirection,
189
  DismissMethod mechanism = rollbackElement,
190 191 192 193 194 195 196 197 198 199
}) async {
  final Finder itemFinder = find.text(item.toString());
  expect(itemFinder, findsOneWidget);

  await mechanism(tester, itemFinder, gestureDirection: gestureDirection);

  await tester.pump(); // start the slide
  await tester.pump(const Duration(milliseconds: 100));
}

200 201 202
Future<void> checkFlingItemAfterMovement(
  WidgetTester tester,
  int item, {
203
  required AxisDirection gestureDirection,
204
  DismissMethod mechanism = rollbackElement,
205 206 207 208 209 210 211 212 213 214
}) async {
  final Finder itemFinder = find.text(item.toString());
  expect(itemFinder, findsOneWidget);

  await mechanism(tester, itemFinder, gestureDirection: gestureDirection);

  await tester.pump(); // start the slide
  await tester.pump(const Duration(milliseconds: 300));
}

215
Future<void> rollbackElement(WidgetTester tester, Finder finder, { required AxisDirection gestureDirection, double initialOffsetFactor = 0.0 }) async {
216 217 218 219 220 221 222 223 224 225 226 227 228 229
  Offset delta;
  switch (gestureDirection) {
    case AxisDirection.left:
      delta = const Offset(-30.0, 0.0);
    case AxisDirection.right:
      delta = const Offset(30.0, 0.0);
    case AxisDirection.up:
      delta = const Offset(0.0, -30.0);
    case AxisDirection.down:
      delta = const Offset(0.0, 30.0);
  }
  await tester.fling(finder, delta, 1000.0, initialOffset: delta * initialOffsetFactor);
}

230
class Test1215DismissibleWidget extends StatelessWidget {
231
  const Test1215DismissibleWidget(this.text, { super.key });
232

Hixie's avatar
Hixie committed
233
  final String text;
234 235

  @override
Hixie's avatar
Hixie committed
236
  Widget build(BuildContext context) {
237
    return Dismissible(
238
      dragStartBehavior: DragStartBehavior.down,
239 240
      key: ObjectKey(text),
      child: AspectRatio(
Hixie's avatar
Hixie committed
241
        aspectRatio: 1.0,
242
        child: Text(text),
243
      ),
Hixie's avatar
Hixie committed
244 245 246 247
    );
  }
}

248
void main() {
249
  setUp(() {
250 251
    // Reset "results" variables.
    reportedDismissDirection = defaultDismissDirection;
252
    dismissedItems = <int>[];
253 254
  });

255
  testWidgets('Horizontal drag triggers dismiss scrollDirection=vertical', (WidgetTester tester) async {
256
    await tester.pumpWidget(
257
      buildTest(),
258
    );
259
    expect(dismissedItems, isEmpty);
260

Ian Hickson's avatar
Ian Hickson committed
261
    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
262
    expect(find.text('0'), findsNothing);
263
    expect(dismissedItems, equals(<int>[0]));
264
    expect(reportedDismissDirection, DismissDirection.startToEnd);
265

Ian Hickson's avatar
Ian Hickson committed
266 267 268 269 270 271
    await dismissItem(tester, 1, gestureDirection: AxisDirection.left);
    expect(find.text('1'), findsNothing);
    expect(dismissedItems, equals(<int>[0, 1]));
    expect(reportedDismissDirection, DismissDirection.endToStart);
  });

272
  testWidgets('Horizontal fling triggers dismiss scrollDirection=vertical', (WidgetTester tester) async {
273
    await tester.pumpWidget(
274
      buildTest(),
275
    );
Ian Hickson's avatar
Ian Hickson committed
276 277 278 279 280 281 282 283 284 285 286 287 288
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right, mechanism: flingElement);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
    expect(reportedDismissDirection, DismissDirection.startToEnd);

    await dismissItem(tester, 1, gestureDirection: AxisDirection.left, mechanism: flingElement);
    expect(find.text('1'), findsNothing);
    expect(dismissedItems, equals(<int>[0, 1]));
    expect(reportedDismissDirection, DismissDirection.endToStart);
  });

289
  testWidgets('Horizontal fling does not trigger at zero offset, but does otherwise', (WidgetTester tester) async {
290 291 292 293 294
    await tester.pumpWidget(
      buildTest(
        startToEndThreshold: 0.95,
      ),
    );
Ian Hickson's avatar
Ian Hickson committed
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right, mechanism: flingElementFromZero);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, equals(<int>[]));

    await dismissItem(tester, 0, gestureDirection: AxisDirection.left, mechanism: flingElementFromZero);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, equals(<int>[]));

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right, mechanism: flingElement);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
    expect(reportedDismissDirection, DismissDirection.startToEnd);

    await dismissItem(tester, 1, gestureDirection: AxisDirection.left, mechanism: flingElement);
311
    expect(find.text('1'), findsNothing);
312
    expect(dismissedItems, equals(<int>[0, 1]));
313
    expect(reportedDismissDirection, DismissDirection.endToStart);
314 315
  });

316
  testWidgets('Vertical drag triggers dismiss scrollDirection=horizontal', (WidgetTester tester) async {
317 318 319 320 321 322
    await tester.pumpWidget(
      buildTest(
        scrollDirection: Axis.horizontal,
        dismissDirection: DismissDirection.vertical,
      ),
    );
323
    expect(dismissedItems, isEmpty);
324

Ian Hickson's avatar
Ian Hickson committed
325
    await dismissItem(tester, 0, gestureDirection: AxisDirection.up);
326
    expect(find.text('0'), findsNothing);
327
    expect(dismissedItems, equals(<int>[0]));
328
    expect(reportedDismissDirection, DismissDirection.up);
329

Ian Hickson's avatar
Ian Hickson committed
330
    await dismissItem(tester, 1, gestureDirection: AxisDirection.down);
331
    expect(find.text('1'), findsNothing);
332
    expect(dismissedItems, equals(<int>[0, 1]));
333
    expect(reportedDismissDirection, DismissDirection.down);
334 335
  });

336
  testWidgets('drag-left with DismissDirection.endToStart triggers dismiss (LTR)', (WidgetTester tester) async {
337 338 339 340 341
    await tester.pumpWidget(
      buildTest(
        dismissDirection: DismissDirection.endToStart,
      ),
    );
342
    expect(dismissedItems, isEmpty);
343

Ian Hickson's avatar
Ian Hickson committed
344
    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
345 346
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
Ian Hickson's avatar
Ian Hickson committed
347
    await dismissItem(tester, 1, gestureDirection: AxisDirection.right);
348

Ian Hickson's avatar
Ian Hickson committed
349
    await dismissItem(tester, 0, gestureDirection: AxisDirection.left);
350
    expect(find.text('0'), findsNothing);
351
    expect(dismissedItems, equals(<int>[0]));
Ian Hickson's avatar
Ian Hickson committed
352
    await dismissItem(tester, 1, gestureDirection: AxisDirection.left);
353 354
  });

355
  testWidgets('drag-right with DismissDirection.startToEnd triggers dismiss (LTR)', (WidgetTester tester) async {
356 357 358 359 360
    await tester.pumpWidget(
      buildTest(
        dismissDirection: DismissDirection.startToEnd,
      ),
    );
361
    expect(dismissedItems, isEmpty);
362

Ian Hickson's avatar
Ian Hickson committed
363 364 365 366 367 368 369 370 371
    await dismissItem(tester, 0, gestureDirection: AxisDirection.left);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
  });

372
  testWidgets('drag-right with DismissDirection.endToStart triggers dismiss (RTL)', (WidgetTester tester) async {
373 374 375 376 377 378
    await tester.pumpWidget(
      buildTest(
        textDirection: TextDirection.rtl,
        dismissDirection: DismissDirection.endToStart,
      ),
    );
Ian Hickson's avatar
Ian Hickson committed
379 380 381 382 383 384 385 386 387 388 389 390

    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.left);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
  });

391
  testWidgets('drag-left with DismissDirection.startToEnd triggers dismiss (RTL)', (WidgetTester tester) async {
392 393 394 395 396 397
    await tester.pumpWidget(
      buildTest(
        textDirection: TextDirection.rtl,
        dismissDirection: DismissDirection.startToEnd,
      ),
    );
Ian Hickson's avatar
Ian Hickson committed
398 399 400
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
401 402
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
Ian Hickson's avatar
Ian Hickson committed
403
    await dismissItem(tester, 1, gestureDirection: AxisDirection.right);
404

Ian Hickson's avatar
Ian Hickson committed
405
    await dismissItem(tester, 0, gestureDirection: AxisDirection.left);
406
    expect(find.text('0'), findsNothing);
407
    expect(dismissedItems, equals(<int>[0]));
Ian Hickson's avatar
Ian Hickson committed
408 409 410
    await dismissItem(tester, 1, gestureDirection: AxisDirection.left);
  });

411
  testWidgets('fling-left with DismissDirection.endToStart triggers dismiss (LTR)', (WidgetTester tester) async {
412 413 414 415 416
    await tester.pumpWidget(
      buildTest(
        dismissDirection: DismissDirection.endToStart,
      ),
    );
Ian Hickson's avatar
Ian Hickson committed
417 418 419 420 421 422 423 424 425 426 427 428 429
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
    await dismissItem(tester, 1, gestureDirection: AxisDirection.right);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.left);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
    await dismissItem(tester, 1, gestureDirection: AxisDirection.left);
  });

430
  testWidgets('fling-right with DismissDirection.startToEnd triggers dismiss (LTR)', (WidgetTester tester) async {
431 432 433 434 435
    await tester.pumpWidget(
      buildTest(
        dismissDirection: DismissDirection.startToEnd,
      ),
    );
Ian Hickson's avatar
Ian Hickson committed
436 437 438 439 440 441 442 443 444 445 446 447

    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.left);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.right);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
  });

448
  testWidgets('fling-right with DismissDirection.endToStart triggers dismiss (RTL)', (WidgetTester tester) async {
449 450 451 452 453 454
    await tester.pumpWidget(
      buildTest(
        textDirection: TextDirection.rtl,
        dismissDirection: DismissDirection.endToStart,
      ),
    );
Ian Hickson's avatar
Ian Hickson committed
455 456 457 458 459 460 461 462 463 464 465
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.left);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.right);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
  });

466
  testWidgets('fling-left with DismissDirection.startToEnd triggers dismiss (RTL)', (WidgetTester tester) async {
467 468 469 470 471 472
    await tester.pumpWidget(
      buildTest(
        textDirection: TextDirection.rtl,
        dismissDirection: DismissDirection.startToEnd,
      ),
    );
Ian Hickson's avatar
Ian Hickson committed
473 474 475 476 477 478 479 480 481 482 483
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.right);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
    await dismissItem(tester, 1, mechanism: flingElement, gestureDirection: AxisDirection.right);

    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.left);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
    await dismissItem(tester, 1, mechanism: flingElement, gestureDirection: AxisDirection.left);
484 485
  });

486
  testWidgets('drag-up with DismissDirection.up triggers dismiss', (WidgetTester tester) async {
487 488 489 490 491 492
    await tester.pumpWidget(
      buildTest(
        scrollDirection: Axis.horizontal,
        dismissDirection: DismissDirection.up,
      ),
    );
493
    expect(dismissedItems, isEmpty);
494

Ian Hickson's avatar
Ian Hickson committed
495
    await dismissItem(tester, 0, gestureDirection: AxisDirection.down);
496 497
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
498

Ian Hickson's avatar
Ian Hickson committed
499
    await dismissItem(tester, 0, gestureDirection: AxisDirection.up);
500
    expect(find.text('0'), findsNothing);
501
    expect(dismissedItems, equals(<int>[0]));
502 503
  });

504
  testWidgets('drag-down with DismissDirection.down triggers dismiss', (WidgetTester tester) async {
505 506 507 508 509 510
    await tester.pumpWidget(
      buildTest(
        scrollDirection: Axis.horizontal,
        dismissDirection: DismissDirection.down,
      ),
    );
511
    expect(dismissedItems, isEmpty);
512

Ian Hickson's avatar
Ian Hickson committed
513 514 515 516 517 518 519 520 521
    await dismissItem(tester, 0, gestureDirection: AxisDirection.up);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.down);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
  });

522
  testWidgets('fling-up with DismissDirection.up triggers dismiss', (WidgetTester tester) async {
523 524 525 526 527 528
    await tester.pumpWidget(
      buildTest(
        scrollDirection: Axis.horizontal,
        dismissDirection: DismissDirection.up,
      ),
    );
Ian Hickson's avatar
Ian Hickson committed
529 530 531 532 533 534 535 536 537 538 539
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.down);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.up);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
  });

540
  testWidgets('fling-down with DismissDirection.down triggers dismiss', (WidgetTester tester) async {
541 542 543 544 545 546
    await tester.pumpWidget(
      buildTest(
        scrollDirection: Axis.horizontal,
        dismissDirection: DismissDirection.down,
      ),
    );
Ian Hickson's avatar
Ian Hickson committed
547 548 549
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.up);
550 551
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
552

Ian Hickson's avatar
Ian Hickson committed
553
    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.down);
554
    expect(find.text('0'), findsNothing);
555
    expect(dismissedItems, equals(<int>[0]));
556
  });
557

558
  testWidgets('drag-left has no effect on dismissible with a high dismiss threshold', (WidgetTester tester) async {
559 560 561 562 563
    await tester.pumpWidget(
      buildTest(
        startToEndThreshold: 1.0,
      ),
    );
564 565
    expect(dismissedItems, isEmpty);

Ian Hickson's avatar
Ian Hickson committed
566 567 568 569 570 571 572 573 574
    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.left);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
  });

575
  testWidgets('fling-left has no effect on dismissible with a high dismiss threshold', (WidgetTester tester) async {
576 577 578 579 580
    await tester.pumpWidget(
      buildTest(
        startToEndThreshold: 1.0,
      ),
    );
Ian Hickson's avatar
Ian Hickson committed
581 582 583
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.right);
584 585 586
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

Ian Hickson's avatar
Ian Hickson committed
587
    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.left);
588 589 590 591
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
  });

592 593 594 595 596 597
  // This is a regression test for an fn2 bug where dragging a card caused an
  // assert "'!_disqualifiedFromEverAppearingAgain' is not true". The old URL
  // was https://github.com/domokit/sky_engine/issues/1068 but that issue is 404
  // now since we migrated to the new repo. The bug was fixed by
  // https://github.com/flutter/engine/pull/1134 at the time, and later made
  // irrelevant by fn3, but just in case...
598
  testWidgets('Verify that drag-move events do not assert', (WidgetTester tester) async {
599 600 601 602 603 604
    await tester.pumpWidget(
      buildTest(
        scrollDirection: Axis.horizontal,
        dismissDirection: DismissDirection.down,
      ),
    );
605
    final Offset location = tester.getTopLeft(find.text('0'));
606
    const Offset offset = Offset(0.0, 5.0);
607
    final TestGesture gesture = await tester.startGesture(location, pointer: 5);
608
    await gesture.moveBy(offset);
609
    await tester.pumpWidget(buildTest());
610
    await gesture.moveBy(offset);
611
    await tester.pumpWidget(buildTest());
612
    await gesture.moveBy(offset);
613
    await tester.pumpWidget(buildTest());
614
    await gesture.moveBy(offset);
615
    await tester.pumpWidget(buildTest());
616
    await gesture.up();
617
  });
Hixie's avatar
Hixie committed
618

619
  // This one is for a case where dismissing a widget above a previously
620
  // dismissed widget threw an exception, which was documented at the
621 622 623
  // now-obsolete URL https://github.com/flutter/engine/issues/1215 (the URL
  // died in the migration to the new repo). Don't copy this test; it doesn't
  // actually remove the dismissed widget, which is a violation of the
624
  // Dismissible contract. This is not an example of good practice.
625
  testWidgets('dismissing bottom then top (smoketest)', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
626
    await tester.pumpWidget(
627
      const Directionality(
Ian Hickson's avatar
Ian Hickson committed
628
        textDirection: TextDirection.ltr,
629
        child: Center(
630
          child: SizedBox(
Ian Hickson's avatar
Ian Hickson committed
631 632
            width: 100.0,
            height: 1000.0,
633
            child: Column(
634
              children: <Widget>[
635 636
                Test1215DismissibleWidget('1'),
                Test1215DismissibleWidget('2'),
Ian Hickson's avatar
Ian Hickson committed
637 638 639
              ],
            ),
          ),
640 641
        ),
      ),
Ian Hickson's avatar
Ian Hickson committed
642
    );
643 644
    expect(find.text('1'), findsOneWidget);
    expect(find.text('2'), findsOneWidget);
Ian Hickson's avatar
Ian Hickson committed
645
    await dismissElement(tester, find.text('2'), gestureDirection: AxisDirection.right);
646
    await tester.pump(); // start the slide away
647
    await tester.pump(const Duration(seconds: 1)); // finish the slide away
648 649
    expect(find.text('1'), findsOneWidget);
    expect(find.text('2'), findsNothing);
Ian Hickson's avatar
Ian Hickson committed
650
    await dismissElement(tester, find.text('1'), gestureDirection: AxisDirection.right);
651
    await tester.pump(); // start the slide away
652
    await tester.pump(const Duration(seconds: 1)); // finish the slide away (at which point the child is no longer included in the tree)
653 654
    expect(find.text('1'), findsNothing);
    expect(find.text('2'), findsNothing);
Hixie's avatar
Hixie committed
655
  });
656

657
  testWidgets('Dismissible starts from the full size when collapsing', (WidgetTester tester) async {
658 659 660 661 662
    await tester.pumpWidget(
      buildTest(
        background: const Text('background'),
      ),
    );
663
    expect(dismissedItems, isEmpty);
664

665
    final Finder itemFinder = find.text('0');
666
    expect(itemFinder, findsOneWidget);
Ian Hickson's avatar
Ian Hickson committed
667
    await dismissElement(tester, itemFinder, gestureDirection: AxisDirection.right);
668
    await tester.pump();
669

670
    expect(find.text('background'), findsOneWidget); // The other four have been culled.
671
    final RenderBox backgroundBox = tester.firstRenderObject(find.text('background'));
672
    expect(backgroundBox.size.height, equals(100.0));
673
  });
674

675
  testWidgets('Checking fling item before movementDuration completes', (WidgetTester tester) async {
676 677 678 679 680 681 682 683 684 685
    await tester.pumpWidget(buildTest());
    expect(dismissedItems, isEmpty);

    await checkFlingItemBeforeMovementEnd(tester, 0, gestureDirection: AxisDirection.left, mechanism: flingElement);
    expect(find.text('0'), findsOneWidget);

    await checkFlingItemBeforeMovementEnd(tester, 1, gestureDirection: AxisDirection.right, mechanism: flingElement);
    expect(find.text('1'), findsOneWidget);
  });

686
  testWidgets('Checking fling item after movementDuration', (WidgetTester tester) async {
687 688 689 690 691 692 693 694 695 696
    await tester.pumpWidget(buildTest());
    expect(dismissedItems, isEmpty);

    await checkFlingItemAfterMovement(tester, 1, gestureDirection: AxisDirection.left, mechanism: flingElement);
    expect(find.text('1'), findsNothing);

    await checkFlingItemAfterMovement(tester, 0, gestureDirection: AxisDirection.right, mechanism: flingElement);
    expect(find.text('0'), findsNothing);
  });

697
  testWidgets('Horizontal fling less than threshold', (WidgetTester tester) async {
698
    await tester.pumpWidget(buildTest(scrollDirection: Axis.horizontal));
699 700
    expect(dismissedItems, isEmpty);

701
    await checkFlingItemAfterMovement(tester, 0, gestureDirection: AxisDirection.left);
702 703 704
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

705
    await checkFlingItemAfterMovement(tester, 1, gestureDirection: AxisDirection.right);
706 707 708 709
    expect(find.text('1'), findsOneWidget);
    expect(dismissedItems, isEmpty);
  });

710
  testWidgets('Vertical fling less than threshold', (WidgetTester tester) async {
711
    await tester.pumpWidget(buildTest());
712 713
    expect(dismissedItems, isEmpty);

714
    await checkFlingItemAfterMovement(tester, 0, gestureDirection: AxisDirection.left);
715 716 717
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

718
    await checkFlingItemAfterMovement(tester, 1, gestureDirection: AxisDirection.right);
719 720 721
    expect(find.text('1'), findsOneWidget);
    expect(dismissedItems, isEmpty);
  });
722

723
  testWidgets('confirmDismiss returns values: true, false, null', (WidgetTester tester) async {
724
    late DismissDirection confirmDismissDirection;
725

726
    Widget buildFrame(bool? confirmDismissValue) {
727
      return buildTest(
728
          confirmDismiss: (BuildContext context, DismissDirection dismissDirection) {
729
          confirmDismissDirection = dismissDirection;
730
          return Future<bool?>.value(confirmDismissValue);
731
        },
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778
      );
    }

    // Dismiss is confirmed IFF confirmDismiss() returns true.
    await tester.pumpWidget(buildFrame(true));
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right, mechanism: flingElement);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
    expect(reportedDismissDirection, DismissDirection.startToEnd);
    expect(confirmDismissDirection, DismissDirection.startToEnd);

    await dismissItem(tester, 1, gestureDirection: AxisDirection.left, mechanism: flingElement);
    expect(find.text('1'), findsNothing);
    expect(dismissedItems, equals(<int>[0, 1]));
    expect(reportedDismissDirection, DismissDirection.endToStart);
    expect(confirmDismissDirection, DismissDirection.endToStart);

    // Dismiss is not confirmed if confirmDismiss() returns false
    dismissedItems = <int>[];
    await tester.pumpWidget(buildFrame(false));

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right, mechanism: flingElement);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
    expect(confirmDismissDirection, DismissDirection.startToEnd);

    await dismissItem(tester, 1, gestureDirection: AxisDirection.left, mechanism: flingElement);
    expect(find.text('1'), findsOneWidget);
    expect(dismissedItems, isEmpty);
    expect(confirmDismissDirection, DismissDirection.endToStart);

    // Dismiss is not confirmed if confirmDismiss() returns null
    dismissedItems = <int>[];
    await tester.pumpWidget(buildFrame(null));

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right, mechanism: flingElement);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
    expect(confirmDismissDirection, DismissDirection.startToEnd);

    await dismissItem(tester, 1, gestureDirection: AxisDirection.left, mechanism: flingElement);
    expect(find.text('1'), findsOneWidget);
    expect(dismissedItems, isEmpty);
    expect(confirmDismissDirection, DismissDirection.endToStart);
  });
779

780
  testWidgets('Pending confirmDismiss does not cause errors', (WidgetTester tester) async {
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841
    // Regression test for https://github.com/flutter/flutter/issues/54990

    late Completer<bool?> completer;
    Widget buildFrame() {
      completer = Completer<bool?>();
      return buildTest(
        confirmDismiss: (BuildContext context, DismissDirection dismissDirection) {
          return completer.future;
        },
      );
    }

    // false for _handleDragEnd - when dragged to the end and released

    await tester.pumpWidget(buildFrame());

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await tester.pumpWidget(const SizedBox());
    completer.complete(false);
    await tester.pump();

    // true for _handleDragEnd - when dragged to the end and released

    await tester.pumpWidget(buildFrame());

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await tester.pumpWidget(const SizedBox());
    completer.complete(true);
    await tester.pump();

    // false for _handleDismissStatusChanged - when fling reaches the end

    await tester.pumpWidget(buildFrame());

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right, mechanism: flingElement);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await tester.pumpWidget(const SizedBox());
    completer.complete(false);
    await tester.pump();

    // true for _handleDismissStatusChanged - when fling reaches the end

    await tester.pumpWidget(buildFrame());

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right, mechanism: flingElement);
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);

    await tester.pumpWidget(const SizedBox());
    completer.complete(true);
    await tester.pump();
  });

842
  testWidgets('Dismissible cannot be dragged with pending confirmDismiss', (WidgetTester tester) async {
843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865
    final Completer<bool?> completer = Completer<bool?>();
    await tester.pumpWidget(
      buildTest(
        confirmDismiss: (BuildContext context, DismissDirection dismissDirection) {
          return completer.future;
        },
      ),
    );

    // Trigger confirmDismiss call.
    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
    final Offset position = tester.getTopLeft(find.text('0'));

    // Try to move and verify it has not moved.
    Offset dragAt = tester.getTopLeft(find.text('0'));
    dragAt = Offset(100.0, dragAt.dy);
    final TestGesture gesture = await tester.startGesture(dragAt);
    await gesture.moveTo(dragAt + const Offset(100.0, 0.0));
    await gesture.up();
    await tester.pump();
    expect(tester.getTopLeft(find.text('0')), position);
  });

866
  testWidgets('Drag to end and release - items does not get stuck if confirmDismiss returns false', (WidgetTester tester) async {
867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
    // Regression test for https://github.com/flutter/flutter/issues/87556

    final Completer<bool?> completer = Completer<bool?>();
    await tester.pumpWidget(
      buildTest(
        confirmDismiss: (BuildContext context, DismissDirection dismissDirection) {
          return completer.future;
        },
      ),
    );

    final Offset position = tester.getTopLeft(find.text('0'));
    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
    completer.complete(false);
    await tester.pumpAndSettle();
    expect(tester.getTopLeft(find.text('0')), position);
  });

885
  testWidgets('Dismissible with null resizeDuration calls onDismissed immediately', (WidgetTester tester) async {
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916
    bool resized = false;
    bool dismissed = false;

    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: Dismissible(
          dragStartBehavior: DragStartBehavior.down,
          key: UniqueKey(),
          resizeDuration: null,
          onDismissed: (DismissDirection direction) {
            dismissed = true;
          },
          onResize: () {
            resized = true;
          },
          child: const SizedBox(
            width: 100.0,
            height: 100.0,
            child: Text('0'),
          ),
        ),
      ),
    );

    await dismissElement(tester, find.text('0'), gestureDirection: AxisDirection.right);
    await tester.pump();
    expect(dismissed, true);
    expect(resized, false);
  });

917
  testWidgets('setState that does not remove the Dismissible from tree should throw Error', (WidgetTester tester) async {
918 919 920 921 922 923
    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.ltr,
      child: StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {
          return ListView(
            dragStartBehavior: DragStartBehavior.down,
924
            itemExtent: 100.0,
925 926 927 928 929 930 931 932 933 934 935 936
            children: <Widget>[
              Dismissible(
                dragStartBehavior: DragStartBehavior.down,
                key: const ValueKey<int>(1),
                onDismissed: (DismissDirection direction) {
                  setState(() {
                    reportedDismissDirection = direction;
                    expect(dismissedItems.contains(1), isFalse);
                    dismissedItems.add(1);
                  });
                },
                crossAxisEndOffset: crossAxisEndOffset,
937
                child: SizedBox(
938 939
                  width: 100.0,
                  height: 100.0,
940 941 942
                  child: Text(1.toString()),
                ),
              ),
943
            ],
944 945 946 947 948 949 950 951 952 953
          );
        },
      ),
    ));
    expect(dismissedItems, isEmpty);
    await dismissItem(tester, 1, gestureDirection: AxisDirection.right);
    expect(dismissedItems, equals(<int>[1]));
    final dynamic exception =  tester.takeException();
    expect(exception, isNotNull);
    expect(exception, isFlutterError);
954
    final FlutterError error = exception as FlutterError;
955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972
    expect(error.diagnostics.last.level, DiagnosticLevel.hint);
    expect(
      error.diagnostics.last.toStringDeep(),
      equalsIgnoringHashCodes(
        'Make sure to implement the onDismissed handler and to immediately\n'
        'remove the Dismissible widget from the application once that\n'
        'handler has fired.\n',
      ),
    );
    expect(
      error.toStringDeep(),
      'FlutterError\n'
      '   A dismissed Dismissible widget is still part of the tree.\n'
      '   Make sure to implement the onDismissed handler and to immediately\n'
      '   remove the Dismissible widget from the application once that\n'
      '   handler has fired.\n',
    );
  });
973

974
  testWidgets('Dismissible.behavior should behave correctly during hit testing', (WidgetTester tester) async {
975 976
    bool didReceivePointerDown = false;

977
    Widget buildStack({required Widget child}) {
978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
      return Directionality(
        textDirection: TextDirection.ltr,
        child: Stack(
          children: <Widget>[
            Listener(
              onPointerDown: (_) {
                didReceivePointerDown = true;
              },
              child: Container(
                width: 100.0,
                height: 100.0,
                color: const Color(0xFF00FF00),
              ),
            ),
            child,
          ],
        ),
      );
    }

    await tester.pumpWidget(
      buildStack(
        child: const Dismissible(
          key: ValueKey<int>(1),
          child: SizedBox(
            width: 100.0,
            height: 100.0,
          ),
        ),
      ),
    );
    await tester.tapAt(const Offset(10.0, 10.0));
    expect(didReceivePointerDown, isFalse);

    Future<void> pumpWidgetTree(HitTestBehavior behavior) {
      return tester.pumpWidget(
        buildStack(
          child: Dismissible(
            key: const ValueKey<int>(1),
            behavior: behavior,
            child: const SizedBox(
              width: 100.0,
              height: 100.0,
            ),
          ),
        ),
      );
    }

    didReceivePointerDown = false;
    await pumpWidgetTree(HitTestBehavior.deferToChild);
    await tester.tapAt(const Offset(10.0, 10.0));
    expect(didReceivePointerDown, isTrue);

    didReceivePointerDown = false;
    await pumpWidgetTree(HitTestBehavior.opaque);
    await tester.tapAt(const Offset(10.0, 10.0));
    expect(didReceivePointerDown, isFalse);

    didReceivePointerDown = false;
    await pumpWidgetTree(HitTestBehavior.translucent);
    await tester.tapAt(const Offset(10.0, 10.0));
    expect(didReceivePointerDown, isTrue);
  });
1042

1043
  testWidgets('DismissDirection.none does not trigger dismiss', (WidgetTester tester) async {
1044 1045 1046 1047
    await tester.pumpWidget(buildTest(
      dismissDirection: DismissDirection.none,
      scrollPhysics: const NeverScrollableScrollPhysics(),
    ));
1048 1049 1050 1051 1052 1053 1054 1055
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.left);
    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
    await dismissItem(tester, 0, gestureDirection: AxisDirection.up);
    await dismissItem(tester, 0, gestureDirection: AxisDirection.down);
    expect(find.text('0'), findsOneWidget);
  });
1056

1057
  testWidgets('DismissDirection.none does not prevent scrolling', (WidgetTester tester) async {
1058 1059
    final ScrollController controller = ScrollController();

1060 1061 1062 1063 1064 1065
    await tester.pumpWidget(
      buildTest(
        controller: controller,
        dismissDirection: DismissDirection.none,
      ),
    );
1066 1067 1068 1069 1070 1071 1072 1073 1074 1075
    expect(dismissedItems, isEmpty);
    expect(controller.offset, 0.0);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.left);
    expect(controller.offset, 0.0);
    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
    expect(controller.offset, 0.0);
    await dismissItem(tester, 0, gestureDirection: AxisDirection.down);
    expect(controller.offset, 0.0);
    await dismissItem(tester, 0, gestureDirection: AxisDirection.up);
1076
    expect(controller.offset, 100.0);
1077
    controller.dispose();
1078
  });
1079

1080
  testWidgets('onUpdate', (WidgetTester tester) async {
1081 1082 1083 1084 1085
    await tester.pumpWidget(buildTest(
      scrollDirection: Axis.horizontal,
    ));
    expect(dismissedItems, isEmpty);

1086 1087 1088 1089
    // Unsuccessful dismiss, fractional progress reported
    await dragItem(tester, 0, gestureDirection: AxisDirection.right, amount: 20);
    expect(reportedDismissUpdateProgress, 0.2);

1090 1091 1092 1093 1094 1095 1096
    // Successful dismiss therefore threshold has been reached
    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.left);
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
    expect(reportedDismissUpdateReachedDirection, DismissDirection.endToStart);
    expect(reportedDismissUpdateReached, true);
    expect(reportedDismissUpdatePreviousReached, true);
1097
    expect(reportedDismissUpdateProgress, 1.0);
1098 1099 1100 1101 1102 1103 1104 1105

    // Unsuccessful dismiss, threshold has not been reached
    await checkFlingItemAfterMovement(tester, 1, gestureDirection: AxisDirection.right);
    expect(find.text('1'), findsOneWidget);
    expect(dismissedItems, equals(<int>[0]));
    expect(reportedDismissUpdateReachedDirection, DismissDirection.startToEnd);
    expect(reportedDismissUpdateReached, false);
    expect(reportedDismissUpdatePreviousReached, false);
1106
    expect(reportedDismissUpdateProgress, 0.0);
1107 1108 1109 1110 1111 1112 1113 1114

    // Another successful dismiss from another direction
    await dismissItem(tester, 1, mechanism: flingElement, gestureDirection: AxisDirection.right);
    expect(find.text('1'), findsNothing);
    expect(dismissedItems, equals(<int>[0, 1]));
    expect(reportedDismissUpdateReachedDirection, DismissDirection.startToEnd);
    expect(reportedDismissUpdateReached, true);
    expect(reportedDismissUpdatePreviousReached, true);
1115
    expect(reportedDismissUpdateProgress, 1.0);
1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130

    await tester.pumpWidget(buildTest(
      scrollDirection: Axis.horizontal,
      confirmDismiss: (BuildContext context, DismissDirection dismissDirection) {
        return Future<bool>.value(false);
      },
    ));

    // Threshold has been reached but dismiss was not confirmed
    await dismissItem(tester, 2, mechanism: flingElement, gestureDirection: AxisDirection.right);
    expect(find.text('2'), findsOneWidget);
    expect(dismissedItems, equals(<int>[0, 1]));
    expect(reportedDismissUpdateReachedDirection, DismissDirection.startToEnd);
    expect(reportedDismissUpdateReached, false);
    expect(reportedDismissUpdatePreviousReached, false);
1131
    expect(reportedDismissUpdateProgress, 0.0);
1132
  });
1133

1134
  testWidgets('Change direction does not lose child state', (WidgetTester tester) async {
1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160
    // Regression test for https://github.com/flutter/flutter/issues/108961
    Widget buildFrame(DismissDirection direction) {
      return Directionality(
        textDirection: TextDirection.ltr,
        child: Dismissible(
          dragStartBehavior: DragStartBehavior.down,
          direction: direction,
          key: const Key('Dismissible'),
          resizeDuration: null,
          child: const SizedBox(
            width: 100.0,
            height: 100.0,
            child: Text('I Love Flutter!'),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildFrame(DismissDirection.horizontal));
    final RenderBox textRenderObjectBegin = tester.renderObject(find.text('I Love Flutter!'));

    await tester.pumpWidget(buildFrame(DismissDirection.none));
    final RenderBox textRenderObjectEnd = tester.renderObject(find.text('I Love Flutter!'));

    expect(identical(textRenderObjectBegin, textRenderObjectEnd), true);
  });
1161
}