dismissible_test.dart 31.3 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
import 'package:flutter/gestures.dart' show DragStartBehavior;
6 7
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
Ian Hickson's avatar
Ian Hickson committed
8
import 'package:flutter_test/flutter_test.dart';
9

10
const double itemExtent = 100.0;
11
Axis scrollDirection = Axis.vertical;
12
DismissDirection dismissDirection = DismissDirection.horizontal;
13
DismissDirection reportedDismissDirection;
14
List<int> dismissedItems = <int>[];
15
Widget background;
16
const double crossAxisEndOffset = 0.5;
17

18 19 20 21 22
Widget buildTest({
  double startToEndThreshold,
  TextDirection textDirection = TextDirection.ltr,
  Future<bool> Function(BuildContext context, DismissDirection direction) confirmDismiss,
}) {
23
  return Directionality(
Ian Hickson's avatar
Ian Hickson committed
24
    textDirection: textDirection,
25
    child: StatefulBuilder(
Ian Hickson's avatar
Ian Hickson committed
26 27
      builder: (BuildContext context, StateSetter setState) {
        Widget buildDismissibleItem(int item) {
28
          return Dismissible(
29
            dragStartBehavior: DragStartBehavior.down,
30
            key: ValueKey<int>(item),
Ian Hickson's avatar
Ian Hickson committed
31
            direction: dismissDirection,
32 33 34
            confirmDismiss: confirmDismiss == null ? null : (DismissDirection direction) {
              return confirmDismiss(context, direction);
            },
Ian Hickson's avatar
Ian Hickson committed
35 36 37 38 39 40 41 42
            onDismissed: (DismissDirection direction) {
              setState(() {
                reportedDismissDirection = direction;
                expect(dismissedItems.contains(item), isFalse);
                dismissedItems.add(item);
              });
            },
            onResize: () {
43
              expect(dismissedItems.contains(item), isFalse);
Ian Hickson's avatar
Ian Hickson committed
44 45 46 47 48
            },
            background: background,
            dismissThresholds: startToEndThreshold == null
                ? <DismissDirection, double>{}
                : <DismissDirection, double>{DismissDirection.startToEnd: startToEndThreshold},
49
            crossAxisEndOffset: crossAxisEndOffset,
50
            child: Container(
Ian Hickson's avatar
Ian Hickson committed
51 52
              width: itemExtent,
              height: itemExtent,
53
              child: Text(item.toString()),
Ian Hickson's avatar
Ian Hickson committed
54 55 56 57
            ),
          );
        }

58
        return Container(
Ian Hickson's avatar
Ian Hickson committed
59
          padding: const EdgeInsets.all(10.0),
60
          child: ListView(
61
            dragStartBehavior: DragStartBehavior.down,
Ian Hickson's avatar
Ian Hickson committed
62 63 64 65
            scrollDirection: scrollDirection,
            itemExtent: itemExtent,
            children: <int>[0, 1, 2, 3, 4]
              .where((int i) => !dismissedItems.contains(i))
66
              .map<Widget>(buildDismissibleItem).toList(),
67 68
          ),
        );
Ian Hickson's avatar
Ian Hickson committed
69 70
      },
    ),
71 72 73
  );
}

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

76
Future<void> dismissElement(WidgetTester tester, Finder finder, { @required AxisDirection gestureDirection }) async {
77 78
  Offset downLocation;
  Offset upLocation;
79
  switch (gestureDirection) {
Ian Hickson's avatar
Ian Hickson committed
80
    case AxisDirection.left:
81
      // getTopRight() returns a point that's just beyond itemWidget's right
82
      // edge and outside the Dismissible event listener's bounds.
83 84
      downLocation = tester.getTopRight(finder) + const Offset(-0.1, 0.0);
      upLocation = tester.getTopLeft(finder);
85
      break;
Ian Hickson's avatar
Ian Hickson committed
86
    case AxisDirection.right:
87
      // we do the same thing here to keep the test symmetric
88 89
      downLocation = tester.getTopLeft(finder) + const Offset(0.1, 0.0);
      upLocation = tester.getTopRight(finder);
90
      break;
Ian Hickson's avatar
Ian Hickson committed
91
    case AxisDirection.up:
92
      // getBottomLeft() returns a point that's just below itemWidget's bottom
93
      // edge and outside the Dismissible event listener's bounds.
94 95
      downLocation = tester.getBottomLeft(finder) + const Offset(0.0, -0.1);
      upLocation = tester.getTopLeft(finder);
96
      break;
Ian Hickson's avatar
Ian Hickson committed
97
    case AxisDirection.down:
98
      // again with doing the same here for symmetry
99 100
      downLocation = tester.getTopLeft(finder) + const Offset(0.1, 0.0);
      upLocation = tester.getBottomLeft(finder);
101
      break;
102
    default:
Ian Hickson's avatar
Ian Hickson committed
103
      fail('unsupported gestureDirection');
104 105
  }

Ian Hickson's avatar
Ian Hickson committed
106
  final TestGesture gesture = await tester.startGesture(downLocation);
107 108
  await gesture.moveTo(upLocation);
  await gesture.up();
Hixie's avatar
Hixie committed
109 110
}

111
Future<void> flingElement(WidgetTester tester, Finder finder, { @required AxisDirection gestureDirection, double initialOffsetFactor = 0.0 }) async {
Ian Hickson's avatar
Ian Hickson committed
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
  Offset delta;
  switch (gestureDirection) {
    case AxisDirection.left:
      delta = const Offset(-300.0, 0.0);
      break;
    case AxisDirection.right:
      delta = const Offset(300.0, 0.0);
      break;
    case AxisDirection.up:
      delta = const Offset(0.0, -300.0);
      break;
    case AxisDirection.down:
      delta = const Offset(0.0, 300.0);
      break;
    default:
      fail('unsupported gestureDirection');
  }
  await tester.fling(finder, delta, 1000.0, initialOffset: delta * initialOffsetFactor);
}
Hixie's avatar
Hixie committed
131

132
Future<void> flingElementFromZero(WidgetTester tester, Finder finder, { @required AxisDirection gestureDirection }) async {
Ian Hickson's avatar
Ian Hickson committed
133 134
  // 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
135
  // started, but with velocity. This is needed to check a boundary condition
Ian Hickson's avatar
Ian Hickson committed
136 137 138 139
  // in the flinging behavior.
  await flingElement(tester, finder, gestureDirection: gestureDirection, initialOffsetFactor: -1.0);
}

140 141 142
Future<void> dismissItem(
  WidgetTester tester,
  int item, {
Ian Hickson's avatar
Ian Hickson committed
143
  @required AxisDirection gestureDirection,
144
  DismissMethod mechanism = dismissElement,
Ian Hickson's avatar
Ian Hickson committed
145 146
}) async {
  assert(gestureDirection != null);
147
  final Finder itemFinder = find.text(item.toString());
148
  expect(itemFinder, findsOneWidget);
Hixie's avatar
Hixie committed
149

Ian Hickson's avatar
Ian Hickson committed
150
  await mechanism(tester, itemFinder, gestureDirection: gestureDirection);
151

152 153 154 155 156
  await tester.pump(); // start the slide
  await tester.pump(const Duration(seconds: 1)); // finish the slide and start shrinking...
  await tester.pump(); // first frame of shrinking animation
  await tester.pump(const Duration(seconds: 1)); // finish the shrinking and call the callback...
  await tester.pump(); // rebuild after the callback removes the entry
157 158
}

159 160 161
Future<void> checkFlingItemBeforeMovementEnd(
  WidgetTester tester,
  int item, {
162
  @required AxisDirection gestureDirection,
163
  DismissMethod mechanism = rollbackElement,
164 165 166 167 168 169 170 171 172 173 174
}) async {
  assert(gestureDirection != null);
  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));
}

175 176 177
Future<void> checkFlingItemAfterMovement(
  WidgetTester tester,
  int item, {
178
  @required AxisDirection gestureDirection,
179
  DismissMethod mechanism = rollbackElement,
180 181 182 183 184 185 186 187 188 189 190
}) async {
  assert(gestureDirection != null);
  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));
}

191
Future<void> rollbackElement(WidgetTester tester, Finder finder, { @required AxisDirection gestureDirection, double initialOffsetFactor = 0.0 }) async {
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
  Offset delta;
  switch (gestureDirection) {
    case AxisDirection.left:
      delta = const Offset(-30.0, 0.0);
      break;
    case AxisDirection.right:
      delta = const Offset(30.0, 0.0);
      break;
    case AxisDirection.up:
      delta = const Offset(0.0, -30.0);
      break;
    case AxisDirection.down:
      delta = const Offset(0.0, 30.0);
      break;
  }
  await tester.fling(finder, delta, 1000.0, initialOffset: delta * initialOffsetFactor);
}

210
class Test1215DismissibleWidget extends StatelessWidget {
211
  const Test1215DismissibleWidget(this.text);
212

Hixie's avatar
Hixie committed
213
  final String text;
214 215

  @override
Hixie's avatar
Hixie committed
216
  Widget build(BuildContext context) {
217
    return Dismissible(
218
      dragStartBehavior: DragStartBehavior.down,
219 220
      key: ObjectKey(text),
      child: AspectRatio(
Hixie's avatar
Hixie committed
221
        aspectRatio: 1.0,
222
        child: Text(text),
223
      ),
Hixie's avatar
Hixie committed
224 225 226 227
    );
  }
}

228
void main() {
229 230 231 232 233
  setUp(() {
    dismissedItems = <int>[];
    background = null;
  });

234
  testWidgets('Horizontal drag triggers dismiss scrollDirection=vertical', (WidgetTester tester) async {
235 236
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;
237

238
    await tester.pumpWidget(buildTest());
239
    expect(dismissedItems, isEmpty);
240

Ian Hickson's avatar
Ian Hickson committed
241
    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
242
    expect(find.text('0'), findsNothing);
243
    expect(dismissedItems, equals(<int>[0]));
244
    expect(reportedDismissDirection, DismissDirection.startToEnd);
245

Ian Hickson's avatar
Ian Hickson committed
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
    await dismissItem(tester, 1, gestureDirection: AxisDirection.left);
    expect(find.text('1'), findsNothing);
    expect(dismissedItems, equals(<int>[0, 1]));
    expect(reportedDismissDirection, DismissDirection.endToStart);
  });

  testWidgets('Horizontal fling triggers dismiss scrollDirection=vertical', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;

    await tester.pumpWidget(buildTest());
    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);
  });

  testWidgets('Horizontal fling does not trigger at zero offset, but does otherwise', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;

    await tester.pumpWidget(buildTest(startToEndThreshold: 0.95));
    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);
291
    expect(find.text('1'), findsNothing);
292
    expect(dismissedItems, equals(<int>[0, 1]));
293
    expect(reportedDismissDirection, DismissDirection.endToStart);
294 295
  });

296
  testWidgets('Vertical drag triggers dismiss scrollDirection=horizontal', (WidgetTester tester) async {
297 298
    scrollDirection = Axis.horizontal;
    dismissDirection = DismissDirection.vertical;
299

300
    await tester.pumpWidget(buildTest());
301
    expect(dismissedItems, isEmpty);
302

Ian Hickson's avatar
Ian Hickson committed
303
    await dismissItem(tester, 0, gestureDirection: AxisDirection.up);
304
    expect(find.text('0'), findsNothing);
305
    expect(dismissedItems, equals(<int>[0]));
306
    expect(reportedDismissDirection, DismissDirection.up);
307

Ian Hickson's avatar
Ian Hickson committed
308
    await dismissItem(tester, 1, gestureDirection: AxisDirection.down);
309
    expect(find.text('1'), findsNothing);
310
    expect(dismissedItems, equals(<int>[0, 1]));
311
    expect(reportedDismissDirection, DismissDirection.down);
312 313
  });

Ian Hickson's avatar
Ian Hickson committed
314
  testWidgets('drag-left with DismissDirection.endToStart triggers dismiss (LTR)', (WidgetTester tester) async {
315 316
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.endToStart;
317

318
    await tester.pumpWidget(buildTest());
319
    expect(dismissedItems, isEmpty);
320

Ian Hickson's avatar
Ian Hickson committed
321
    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
322 323
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
Ian Hickson's avatar
Ian Hickson committed
324
    await dismissItem(tester, 1, gestureDirection: AxisDirection.right);
325

Ian Hickson's avatar
Ian Hickson committed
326
    await dismissItem(tester, 0, gestureDirection: AxisDirection.left);
327
    expect(find.text('0'), findsNothing);
328
    expect(dismissedItems, equals(<int>[0]));
Ian Hickson's avatar
Ian Hickson committed
329
    await dismissItem(tester, 1, gestureDirection: AxisDirection.left);
330 331
  });

Ian Hickson's avatar
Ian Hickson committed
332
  testWidgets('drag-right with DismissDirection.startToEnd triggers dismiss (LTR)', (WidgetTester tester) async {
333 334
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.startToEnd;
335

336
    await tester.pumpWidget(buildTest());
337
    expect(dismissedItems, isEmpty);
338

Ian Hickson's avatar
Ian Hickson committed
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 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]));
  });

  testWidgets('drag-right with DismissDirection.endToStart triggers dismiss (RTL)', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.endToStart;

    await tester.pumpWidget(buildTest(textDirection: TextDirection.rtl));
    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]));
  });

  testWidgets('drag-left with DismissDirection.startToEnd triggers dismiss (RTL)', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.startToEnd;

    await tester.pumpWidget(buildTest(textDirection: TextDirection.rtl));
    expect(dismissedItems, isEmpty);

    await dismissItem(tester, 0, gestureDirection: AxisDirection.right);
372 373
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
Ian Hickson's avatar
Ian Hickson committed
374
    await dismissItem(tester, 1, gestureDirection: AxisDirection.right);
375

Ian Hickson's avatar
Ian Hickson committed
376
    await dismissItem(tester, 0, gestureDirection: AxisDirection.left);
377
    expect(find.text('0'), findsNothing);
378
    expect(dismissedItems, equals(<int>[0]));
Ian Hickson's avatar
Ian Hickson committed
379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
    await dismissItem(tester, 1, gestureDirection: AxisDirection.left);
  });

  testWidgets('fling-left with DismissDirection.endToStart triggers dismiss (LTR)', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.endToStart;

    await tester.pumpWidget(buildTest());
    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);
  });

  testWidgets('fling-right with DismissDirection.startToEnd triggers dismiss (LTR)', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.startToEnd;

    await tester.pumpWidget(buildTest());
    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]));
  });

  testWidgets('fling-right with DismissDirection.endToStart triggers dismiss (RTL)', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.endToStart;

    await tester.pumpWidget(buildTest(textDirection: TextDirection.rtl));
    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]));
  });

  testWidgets('fling-left with DismissDirection.startToEnd triggers dismiss (RTL)', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.startToEnd;

    await tester.pumpWidget(buildTest(textDirection: TextDirection.rtl));
    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);
448 449
  });

450
  testWidgets('drag-up with DismissDirection.up triggers dismiss', (WidgetTester tester) async {
451 452
    scrollDirection = Axis.horizontal;
    dismissDirection = DismissDirection.up;
453

454
    await tester.pumpWidget(buildTest());
455
    expect(dismissedItems, isEmpty);
456

Ian Hickson's avatar
Ian Hickson committed
457
    await dismissItem(tester, 0, gestureDirection: AxisDirection.down);
458 459
    expect(find.text('0'), findsOneWidget);
    expect(dismissedItems, isEmpty);
460

Ian Hickson's avatar
Ian Hickson committed
461
    await dismissItem(tester, 0, gestureDirection: AxisDirection.up);
462
    expect(find.text('0'), findsNothing);
463
    expect(dismissedItems, equals(<int>[0]));
464 465
  });

466
  testWidgets('drag-down with DismissDirection.down triggers dismiss', (WidgetTester tester) async {
467 468
    scrollDirection = Axis.horizontal;
    dismissDirection = DismissDirection.down;
469

470
    await tester.pumpWidget(buildTest());
471
    expect(dismissedItems, isEmpty);
472

Ian Hickson's avatar
Ian Hickson committed
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
    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]));
  });

  testWidgets('fling-up with DismissDirection.up triggers dismiss', (WidgetTester tester) async {
    scrollDirection = Axis.horizontal;
    dismissDirection = DismissDirection.up;

    await tester.pumpWidget(buildTest());
    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]));
  });

  testWidgets('fling-down with DismissDirection.down triggers dismiss', (WidgetTester tester) async {
    scrollDirection = Axis.horizontal;
    dismissDirection = DismissDirection.down;

    await tester.pumpWidget(buildTest());
    expect(dismissedItems, isEmpty);

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

Ian Hickson's avatar
Ian Hickson committed
509
    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.down);
510
    expect(find.text('0'), findsNothing);
511
    expect(dismissedItems, equals(<int>[0]));
512
  });
513

514
  testWidgets('drag-left has no effect on dismissible with a high dismiss threshold', (WidgetTester tester) async {
515 516 517 518 519 520
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;

    await tester.pumpWidget(buildTest(startToEndThreshold: 1.0));
    expect(dismissedItems, isEmpty);

Ian Hickson's avatar
Ian Hickson committed
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537
    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]));
  });

  testWidgets('fling-left has no effect on dismissible with a high dismiss threshold', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;

    await tester.pumpWidget(buildTest(startToEndThreshold: 1.0));
    expect(dismissedItems, isEmpty);

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

Ian Hickson's avatar
Ian Hickson committed
541
    await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.left);
542 543 544 545
    expect(find.text('0'), findsNothing);
    expect(dismissedItems, equals(<int>[0]));
  });

546 547 548 549 550 551
  // 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...
552
  testWidgets('Verify that drag-move events do not assert', (WidgetTester tester) async {
553 554 555
    scrollDirection = Axis.horizontal;
    dismissDirection = DismissDirection.down;

556
    await tester.pumpWidget(buildTest());
557
    final Offset location = tester.getTopLeft(find.text('0'));
558
    const Offset offset = Offset(0.0, 5.0);
559
    final TestGesture gesture = await tester.startGesture(location, pointer: 5);
560
    await gesture.moveBy(offset);
561
    await tester.pumpWidget(buildTest());
562
    await gesture.moveBy(offset);
563
    await tester.pumpWidget(buildTest());
564
    await gesture.moveBy(offset);
565
    await tester.pumpWidget(buildTest());
566
    await gesture.moveBy(offset);
567
    await tester.pumpWidget(buildTest());
568
    await gesture.up();
569
  });
Hixie's avatar
Hixie committed
570

571
  // This one is for a case where dismissing a widget above a previously
572
  // dismissed widget threw an exception, which was documented at the
573 574 575
  // 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
576
  // Dismissible contract. This is not an example of good practice.
577
  testWidgets('dismissing bottom then top (smoketest)', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
578
    await tester.pumpWidget(
579
      Directionality(
Ian Hickson's avatar
Ian Hickson committed
580
        textDirection: TextDirection.ltr,
581 582
        child: Center(
          child: Container(
Ian Hickson's avatar
Ian Hickson committed
583 584
            width: 100.0,
            height: 1000.0,
585
            child: Column(
586
              children: const <Widget>[
587 588
                Test1215DismissibleWidget('1'),
                Test1215DismissibleWidget('2'),
Ian Hickson's avatar
Ian Hickson committed
589 590 591
              ],
            ),
          ),
592 593
        ),
      ),
Ian Hickson's avatar
Ian Hickson committed
594
    );
595 596
    expect(find.text('1'), findsOneWidget);
    expect(find.text('2'), findsOneWidget);
Ian Hickson's avatar
Ian Hickson committed
597
    await dismissElement(tester, find.text('2'), gestureDirection: AxisDirection.right);
598
    await tester.pump(); // start the slide away
599
    await tester.pump(const Duration(seconds: 1)); // finish the slide away
600 601
    expect(find.text('1'), findsOneWidget);
    expect(find.text('2'), findsNothing);
Ian Hickson's avatar
Ian Hickson committed
602
    await dismissElement(tester, find.text('1'), gestureDirection: AxisDirection.right);
603
    await tester.pump(); // start the slide away
604
    await tester.pump(const Duration(seconds: 1)); // finish the slide away (at which point the child is no longer included in the tree)
605 606
    expect(find.text('1'), findsNothing);
    expect(find.text('2'), findsNothing);
Hixie's avatar
Hixie committed
607
  });
608

609
  testWidgets('Dismissible starts from the full size when collapsing', (WidgetTester tester) async {
610 611
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;
612
    background = const Text('background');
613

614
    await tester.pumpWidget(buildTest());
615
    expect(dismissedItems, isEmpty);
616

617
    final Finder itemFinder = find.text('0');
618
    expect(itemFinder, findsOneWidget);
Ian Hickson's avatar
Ian Hickson committed
619
    await dismissElement(tester, itemFinder, gestureDirection: AxisDirection.right);
620
    await tester.pump();
621

622
    expect(find.text('background'), findsOneWidget); // The other four have been culled.
623
    final RenderBox backgroundBox = tester.firstRenderObject(find.text('background'));
624
    expect(backgroundBox.size.height, equals(100.0));
625
  });
626 627 628 629 630 631 632 633 634 635 636 637

  testWidgets('Checking fling item before movementDuration completes', (WidgetTester tester) async {
    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);
  });

638
  testWidgets('Checking fling item after movementDuration', (WidgetTester tester) async {
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675
    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);
  });

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

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

    await checkFlingItemAfterMovement(tester, 1, gestureDirection: AxisDirection.right, mechanism: rollbackElement);
    expect(find.text('1'), findsOneWidget);
    expect(dismissedItems, isEmpty);
  });

  testWidgets('Vertical fling less than threshold', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    await tester.pumpWidget(buildTest());
    expect(dismissedItems, isEmpty);

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

    await checkFlingItemAfterMovement(tester, 1, gestureDirection: AxisDirection.right, mechanism: rollbackElement);
    expect(find.text('1'), findsOneWidget);
    expect(dismissedItems, isEmpty);
  });
676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734

  testWidgets('confirmDismiss returns values: true, false, null', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;
    DismissDirection confirmDismissDirection;

    Widget buildFrame(bool confirmDismissValue) {
      return buildTest(
        confirmDismiss: (BuildContext context, DismissDirection dismissDirection) {
          confirmDismissDirection = dismissDirection;
          return Future<bool>.value(confirmDismissValue);
        }
      );
    }

    // 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);
  });
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 779

  testWidgets('setState that does not remove the Dismissible from tree should throws Error', (WidgetTester tester) async {
    scrollDirection = Axis.vertical;
    dismissDirection = DismissDirection.horizontal;

    await tester.pumpWidget(Directionality(
      textDirection: TextDirection.ltr,
      child: StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {
          return ListView(
            dragStartBehavior: DragStartBehavior.down,
            scrollDirection: scrollDirection,
            itemExtent: itemExtent,
            children: <Widget>[
              Dismissible(
                dragStartBehavior: DragStartBehavior.down,
                key: const ValueKey<int>(1),
                direction: dismissDirection,
                onDismissed: (DismissDirection direction) {
                  setState(() {
                    reportedDismissDirection = direction;
                    expect(dismissedItems.contains(1), isFalse);
                    dismissedItems.add(1);
                  });
                },
                background: background,
                dismissThresholds: const <DismissDirection, double>{},
                crossAxisEndOffset: crossAxisEndOffset,
                child: Container(
                  width: itemExtent,
                  height: itemExtent,
                  child: Text(1.toString()),
                ),
              ),
            ]
          );
        },
      ),
    ));
    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);
780
    final FlutterError error = exception as FlutterError;
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798
    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',
    );
  });
799
}