widget_tester_test.dart 25.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
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 'dart:async';
6
import 'dart:io';
7
import 'dart:ui';
8

9
import 'package:flutter/foundation.dart';
10 11
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
12
import 'package:flutter/rendering.dart';
13
import 'package:flutter/scheduler.dart';
14
import 'package:flutter_test/flutter_test.dart';
15 16

// ignore: deprecated_member_use
17 18
import 'package:test_api/test_api.dart' as test_package;
import 'package:test_api/src/frontend/async_matcher.dart' show AsyncMatcher;
19

20 21 22
const List<Widget> fooBarTexts = <Text>[
  Text('foo', textDirection: TextDirection.ltr),
  Text('bar', textDirection: TextDirection.ltr),
Ian Hickson's avatar
Ian Hickson committed
23 24
];

25
void main() {
26 27 28 29 30 31 32 33 34 35
  group('getSemanticsData', () {
    testWidgets('throws when there are no semantics', (WidgetTester tester) async {
      await tester.pumpWidget(
        const MaterialApp(
          home: Scaffold(
            body: Text('hello'),
          ),
        ),
      );

Dan Field's avatar
Dan Field committed
36
      expect(() => tester.getSemantics(find.text('hello')), throwsStateError);
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
    }, semanticsEnabled: false);

    testWidgets('throws when there are multiple results from the finder', (WidgetTester tester) async {
      final SemanticsHandle semanticsHandle = tester.ensureSemantics();

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: Row(
              children: const <Widget>[
                Text('hello'),
                Text('hello'),
              ],
            ),
          ),
        ),
      );

Dan Field's avatar
Dan Field committed
55
      expect(() => tester.getSemantics(find.text('hello')), throwsStateError);
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
      semanticsHandle.dispose();
    });

    testWidgets('Returns the correct SemanticsData', (WidgetTester tester) async {
      final SemanticsHandle semanticsHandle = tester.ensureSemantics();

      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: Container(
              child: OutlineButton(
                  onPressed: () { },
                  child: const Text('hello'),
              ),
            ),
          ),
        ),
      );

      final SemanticsNode node = tester.getSemantics(find.text('hello'));
      final SemanticsData semantics = node.getSemanticsData();
      expect(semantics.label, 'hello');
      expect(semantics.hasAction(SemanticsAction.tap), true);
      expect(semantics.hasFlag(SemanticsFlag.isButton), true);
      semanticsHandle.dispose();
    });

    testWidgets('Can enable semantics for tests via semanticsEnabled', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: Container(
              child: OutlineButton(
                  onPressed: () { },
                  child: const Text('hello'),
              ),
            ),
          ),
        ),
      );

      final SemanticsNode node = tester.getSemantics(find.text('hello'));
      final SemanticsData semantics = node.getSemanticsData();
      expect(semantics.label, 'hello');
      expect(semantics.hasAction(SemanticsAction.tap), true);
      expect(semantics.hasFlag(SemanticsFlag.isButton), true);
    }, semanticsEnabled: true);

    testWidgets('Returns merged SemanticsData', (WidgetTester tester) async {
      final SemanticsHandle semanticsHandle = tester.ensureSemantics();
      const Key key = Key('test');
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: Semantics(
              label: 'A',
              child: Semantics(
                label: 'B',
                child: Semantics(
                  key: key,
                  label: 'C',
                  child: Container(),
                ),
              ),
            ),
          ),
        ),
      );

      final SemanticsNode node = tester.getSemantics(find.byKey(key));
      final SemanticsData semantics = node.getSemanticsData();
      expect(semantics.label, 'A\nB\nC');
      semanticsHandle.dispose();
    });
  });

  group('ensureVisible', () {
    testWidgets('scrolls to make widget visible', (WidgetTester tester) async {
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: ListView.builder(
              itemCount: 20,
              shrinkWrap: true,
              itemBuilder: (BuildContext context, int i) => ListTile(title: Text('Item $i')),
           ),
         ),
        ),
      );

      // Make sure widget isn't on screen
      expect(find.text('Item 15', skipOffstage: true), findsNothing);

      await tester.ensureVisible(find.text('Item 15', skipOffstage: false));
      await tester.pumpAndSettle();

      expect(find.text('Item 15', skipOffstage: true), findsOneWidget);
    });
  });

156 157
  group('expectLater', () {
    testWidgets('completes when matcher completes', (WidgetTester tester) async {
158 159
      final Completer<void> completer = Completer<void>();
      final Future<void> future = expectLater(null, FakeMatcher(completer));
160 161 162
      String result;
      future.then<void>((void value) {
        result = '123';
163
      });
164
      test_package.expect(result, isNull);
165
      completer.complete();
166
      test_package.expect(result, isNull);
167 168
      await future;
      await tester.pump();
169
      test_package.expect(result, '123');
170
    });
171 172

    testWidgets('respects the skip flag', (WidgetTester tester) async {
173 174
      final Completer<void> completer = Completer<void>();
      final Future<void> future = expectLater(null, FakeMatcher(completer), skip: 'testing skip');
175
      bool completed = false;
176
      future.then<void>((_) {
177 178 179 180 181
        completed = true;
      });
      test_package.expect(completed, isFalse);
      await future;
      test_package.expect(completed, isTrue);
182
    });
183 184
  });

185
  group('findsOneWidget', () {
186
    testWidgets('finds exactly one widget', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
187
      await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
188
      expect(find.text('foo'), findsOneWidget);
189 190
    });

191
    testWidgets('fails with a descriptive message', (WidgetTester tester) async {
192 193
      TestFailure failure;
      try {
194
        expect(find.text('foo', skipOffstage: false), findsOneWidget);
195
      } on TestFailure catch (e) {
196 197 198 199
        failure = e;
      }

      expect(failure, isNotNull);
200
      final String message = failure.message;
201
      expect(message, contains('Expected: exactly one matching node in the widget tree\n'));
202
      expect(message, contains('Actual: _TextFinder:<zero widgets with text "foo">\n'));
203
      expect(message, contains('Which: means none were found but one was expected\n'));
204 205 206
    });
  });

207
  group('findsNothing', () {
208
    testWidgets('finds no widgets', (WidgetTester tester) async {
209
      expect(find.text('foo'), findsNothing);
210 211
    });

212
    testWidgets('fails with a descriptive message', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
213
      await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
214 215 216

      TestFailure failure;
      try {
217
        expect(find.text('foo', skipOffstage: false), findsNothing);
218
      } on TestFailure catch (e) {
219 220 221 222
        failure = e;
      }

      expect(failure, isNotNull);
223
      final String message = failure.message;
224 225

      expect(message, contains('Expected: no matching nodes in the widget tree\n'));
226
      expect(message, contains('Actual: _TextFinder:<exactly one widget with text "foo": Text("foo", textDirection: ltr)>\n'));
227
      expect(message, contains('Which: means one was found but none were expected\n'));
228
    });
229 230

    testWidgets('fails with a descriptive message when skipping', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
231
      await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
232 233 234 235

      TestFailure failure;
      try {
        expect(find.text('foo'), findsNothing);
236
      } on TestFailure catch (e) {
237 238 239 240
        failure = e;
      }

      expect(failure, isNotNull);
241
      final String message = failure.message;
242 243

      expect(message, contains('Expected: no matching nodes in the widget tree\n'));
244
      expect(message, contains('Actual: _TextFinder:<exactly one widget with text "foo" (ignoring offstage widgets): Text("foo", textDirection: ltr)>\n'));
245 246
      expect(message, contains('Which: means one was found but none were expected\n'));
    });
247 248

    testWidgets('pumping', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
249
      await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
250 251
      int count;

252
      final AnimationController test = AnimationController(
253 254 255
        duration: const Duration(milliseconds: 5100),
        vsync: tester,
      );
256 257
      count = await tester.pumpAndSettle(const Duration(seconds: 1));
      expect(count, 1); // it always pumps at least one frame
258 259

      test.forward(from: 0.0);
260
      count = await tester.pumpAndSettle(const Duration(seconds: 1));
261 262 263 264 265 266 267 268 269 270 271
      // 1 frame at t=0, starting the animation
      // 1 frame at t=1
      // 1 frame at t=2
      // 1 frame at t=3
      // 1 frame at t=4
      // 1 frame at t=5
      // 1 frame at t=6, ending the animation
      expect(count, 7);

      test.forward(from: 0.0);
      await tester.pump(); // starts the animation
272
      count = await tester.pumpAndSettle(const Duration(seconds: 1));
273 274 275 276 277
      expect(count, 6);

      test.forward(from: 0.0);
      await tester.pump(); // starts the animation
      await tester.pump(); // has no effect
278
      count = await tester.pumpAndSettle(const Duration(seconds: 1));
279 280
      expect(count, 6);
    });
281
  });
282

283 284
  group('find.byElementPredicate', () {
    testWidgets('fails with a custom description in the message', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
285
      await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
286

287
      const String customDescription = 'custom description';
288 289 290
      TestFailure failure;
      try {
        expect(find.byElementPredicate((_) => false, description: customDescription), findsOneWidget);
291
      } on TestFailure catch (e) {
292 293 294 295
        failure = e;
      }

      expect(failure, isNotNull);
296
      expect(failure.message, contains('Actual: _ElementPredicateFinder:<zero widgets with $customDescription'));
297 298 299 300 301
    });
  });

  group('find.byWidgetPredicate', () {
    testWidgets('fails with a custom description in the message', (WidgetTester tester) async {
Ian Hickson's avatar
Ian Hickson committed
302
      await tester.pumpWidget(const Text('foo', textDirection: TextDirection.ltr));
303

304
      const String customDescription = 'custom description';
305 306 307
      TestFailure failure;
      try {
        expect(find.byWidgetPredicate((_) => false, description: customDescription), findsOneWidget);
308
      } on TestFailure catch (e) {
309 310 311 312
        failure = e;
      }

      expect(failure, isNotNull);
313
      expect(failure.message, contains('Actual: _WidgetPredicateFinder:<zero widgets with $customDescription'));
314 315
    });
  });
316 317 318

  group('find.descendant', () {
    testWidgets('finds one descendant', (WidgetTester tester) async {
319
      await tester.pumpWidget(Row(
320 321
        textDirection: TextDirection.ltr,
        children: <Widget>[
322
          Column(children: fooBarTexts),
323 324
        ],
      ));
325 326 327

      expect(find.descendant(
        of: find.widgetWithText(Row, 'foo'),
328
        matching: find.text('bar'),
329 330 331 332
      ), findsOneWidget);
    });

    testWidgets('finds two descendants with different ancestors', (WidgetTester tester) async {
333
      await tester.pumpWidget(Row(
334 335
        textDirection: TextDirection.ltr,
        children: <Widget>[
336 337
          Column(children: fooBarTexts),
          Column(children: fooBarTexts),
338 339
        ],
      ));
340 341 342

      expect(find.descendant(
        of: find.widgetWithText(Column, 'foo'),
343
        matching: find.text('bar'),
344 345 346 347
      ), findsNWidgets(2));
    });

    testWidgets('fails with a descriptive message', (WidgetTester tester) async {
348
      await tester.pumpWidget(Row(
349 350
        textDirection: TextDirection.ltr,
        children: <Widget>[
351
          Column(children: const <Text>[Text('foo', textDirection: TextDirection.ltr)]),
Ian Hickson's avatar
Ian Hickson committed
352
          const Text('bar', textDirection: TextDirection.ltr),
353 354
        ],
      ));
355 356 357 358 359

      TestFailure failure;
      try {
        expect(find.descendant(
          of: find.widgetWithText(Column, 'foo'),
360
          matching: find.text('bar'),
361
        ), findsOneWidget);
362
      } on TestFailure catch (e) {
363 364 365 366 367
        failure = e;
      }

      expect(failure, isNotNull);
      expect(
368
        failure.message,
369
        contains(
370
          'Actual: _DescendantFinder:<zero widgets with text "bar" that has ancestor(s) with type "Column" which is an ancestor of text "foo"',
371
        ),
372
      );
373
    });
374
  });
375

376 377
  group('find.ancestor', () {
    testWidgets('finds one ancestor', (WidgetTester tester) async {
378
      await tester.pumpWidget(Row(
379 380
        textDirection: TextDirection.ltr,
        children: <Widget>[
381
          Column(children: fooBarTexts),
382 383
        ],
      ));
384

385 386 387 388 389 390 391 392
      expect(find.ancestor(
        of: find.text('bar'),
        matching: find.widgetWithText(Row, 'foo'),
      ), findsOneWidget);
    });

    testWidgets('finds two matching ancestors, one descendant', (WidgetTester tester) async {
      await tester.pumpWidget(
393
        Directionality(
394
          textDirection: TextDirection.ltr,
395
          child: Row(
396
            children: <Widget>[
397
              Row(children: fooBarTexts),
398 399 400 401 402 403 404
            ],
          ),
        ),
      );

      expect(find.ancestor(
        of: find.text('bar'),
405
        matching: find.byType(Row),
406 407 408 409
      ), findsNWidgets(2));
    });

    testWidgets('fails with a descriptive message', (WidgetTester tester) async {
410
      await tester.pumpWidget(Row(
411 412
        textDirection: TextDirection.ltr,
        children: <Widget>[
413
          Column(children: const <Text>[Text('foo', textDirection: TextDirection.ltr)]),
414 415 416 417 418 419 420 421 422 423
          const Text('bar', textDirection: TextDirection.ltr),
        ],
      ));

      TestFailure failure;
      try {
        expect(find.ancestor(
          of: find.text('bar'),
          matching: find.widgetWithText(Column, 'foo'),
        ), findsOneWidget);
424
      } on TestFailure catch (e) {
425 426 427 428 429 430
        failure = e;
      }

      expect(failure, isNotNull);
      expect(
        failure.message,
431
        contains(
432
          'Actual: _AncestorFinder:<zero widgets with type "Column" which is an ancestor of text "foo" which is an ancestor of text "bar"',
433
        ),
434 435 436 437
      );
    });

    testWidgets('Root not matched by default', (WidgetTester tester) async {
438
      await tester.pumpWidget(Row(
439 440
        textDirection: TextDirection.ltr,
        children: <Widget>[
441
          Column(children: fooBarTexts),
442 443 444 445 446 447
        ],
      ));

      expect(find.ancestor(
        of: find.byType(Column),
        matching: find.widgetWithText(Column, 'foo'),
448 449 450 451
      ), findsNothing);
    });

    testWidgets('Match the root', (WidgetTester tester) async {
452
      await tester.pumpWidget(Row(
453 454
        textDirection: TextDirection.ltr,
        children: <Widget>[
455
          Column(children: fooBarTexts),
456 457
        ],
      ));
458 459

      expect(find.descendant(
460 461
        of: find.byType(Column),
        matching: find.widgetWithText(Column, 'foo'),
462 463 464
        matchRoot: true,
      ), findsOneWidget);
    });
465
  });
466

467
  group('pageBack', () {
468
    testWidgets('fails when there are no back buttons', (WidgetTester tester) async {
469
      await tester.pumpWidget(Container());
470 471 472

      expect(
        expectAsync0(tester.pageBack),
Dan Field's avatar
Dan Field committed
473
        throwsA(isA<TestFailure>()),
474 475 476 477 478
      );
    });

    testWidgets('successfully taps material back buttons', (WidgetTester tester) async {
      await tester.pumpWidget(
479 480 481
        MaterialApp(
          home: Center(
            child: Builder(
482
              builder: (BuildContext context) {
483
                return RaisedButton(
484 485
                  child: const Text('Next'),
                  onPressed: () {
486
                    Navigator.push<void>(context, MaterialPageRoute<void>(
487
                      builder: (BuildContext context) {
488 489
                        return Scaffold(
                          appBar: AppBar(
490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516
                            title: const Text('Page 2'),
                          ),
                        );
                      },
                    ));
                  },
                );
              } ,
            ),
          ),
        ),
      );

      await tester.tap(find.text('Next'));
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 400));

      await tester.pageBack();
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 400));

      expect(find.text('Next'), findsOneWidget);
      expect(find.text('Page 2'), findsNothing);
    });

    testWidgets('successfully taps cupertino back buttons', (WidgetTester tester) async {
      await tester.pumpWidget(
517 518 519
        MaterialApp(
          home: Center(
            child: Builder(
520
              builder: (BuildContext context) {
521
                return CupertinoButton(
522 523
                  child: const Text('Next'),
                  onPressed: () {
524
                    Navigator.push<void>(context, CupertinoPageRoute<void>(
525
                      builder: (BuildContext context) {
526
                        return CupertinoPageScaffold(
527
                          navigationBar: const CupertinoNavigationBar(
528
                            middle: Text('Page 2'),
529
                          ),
530
                          child: Container(),
531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
                        );
                      },
                    ));
                  },
                );
              } ,
            ),
          ),
        ),
      );

      await tester.tap(find.text('Next'));
      await tester.pump();
      await tester.pump(const Duration(milliseconds: 400));

      await tester.pageBack();
      await tester.pump();
548
      await tester.pumpAndSettle();
549 550 551 552 553 554

      expect(find.text('Next'), findsOneWidget);
      expect(find.text('Page 2'), findsNothing);
    });
  });

555
  testWidgets('hasRunningAnimations control test', (WidgetTester tester) async {
556
    final AnimationController controller = AnimationController(
557
      duration: const Duration(seconds: 1),
558
      vsync: const TestVSync(),
559 560 561 562 563 564 565 566 567 568 569
    );
    expect(tester.hasRunningAnimations, isFalse);
    controller.forward();
    expect(tester.hasRunningAnimations, isTrue);
    controller.stop();
    expect(tester.hasRunningAnimations, isFalse);
    controller.forward();
    expect(tester.hasRunningAnimations, isTrue);
    await tester.pumpAndSettle();
    expect(tester.hasRunningAnimations, isFalse);
  });
570 571

  testWidgets('pumpAndSettle control test', (WidgetTester tester) async {
572
    final AnimationController controller = AnimationController(
573
      duration: const Duration(minutes: 525600),
574
      vsync: const TestVSync(),
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
    );
    expect(await tester.pumpAndSettle(), 1);
    controller.forward();
    try {
      await tester.pumpAndSettle();
      expect(true, isFalse);
    } catch (e) {
      expect(e, isFlutterError);
    }
    controller.stop();
    expect(await tester.pumpAndSettle(), 1);
    controller.duration = const Duration(seconds: 1);
    controller.forward();
    expect(await tester.pumpAndSettle(const Duration(milliseconds: 300)), 5); // 0, 300, 600, 900, 1200ms
  });
590 591 592 593 594 595 596 597 598 599 600

  group('runAsync', () {
    testWidgets('works with no async calls', (WidgetTester tester) async {
      String value;
      await tester.runAsync(() async {
        value = '123';
      });
      expect(value, '123');
    });

    testWidgets('works with real async calls', (WidgetTester tester) async {
601
      final StringBuffer buf = StringBuffer('1');
602 603
      await tester.runAsync(() async {
        buf.write('2');
604
        //ignore: avoid_slow_async_io
605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620
        await Directory.current.stat();
        buf.write('3');
      });
      buf.write('4');
      expect(buf.toString(), '1234');
    });

    testWidgets('propagates return values', (WidgetTester tester) async {
      final String value = await tester.runAsync<String>(() async {
        return '123';
      });
      expect(value, '123');
    });

    testWidgets('reports errors via framework', (WidgetTester tester) async {
      final String value = await tester.runAsync<String>(() async {
621
        throw ArgumentError();
622 623 624 625 626 627
      });
      expect(value, isNull);
      expect(tester.takeException(), isArgumentError);
    });

    testWidgets('disallows re-entry', (WidgetTester tester) async {
628
      final Completer<void> completer = Completer<void>();
629
      tester.runAsync<void>(() => completer.future);
Dan Field's avatar
Dan Field committed
630
      expect(() => tester.runAsync(() async { }), throwsA(isA<TestFailure>()));
631 632
      completer.complete();
    });
633 634

    testWidgets('maintains existing zone values', (WidgetTester tester) async {
635
      final Object key = Object();
636
      await runZoned<Future<void>>(() {
637
        expect(Zone.current[key], 'abczed');
638
        return tester.runAsync<void>(() async {
639 640 641 642 643 644
          expect(Zone.current[key], 'abczed');
        });
      }, zoneValues: <dynamic, dynamic>{
        key: 'abczed',
      });
    });
645
  });
646 647 648

  testWidgets('showKeyboard can be called twice', (WidgetTester tester) async {
    await tester.pumpWidget(
649 650 651 652
      MaterialApp(
        home: Material(
          child: Center(
            child: TextFormField(),
653 654 655 656 657
          ),
        ),
      ),
    );
    await tester.showKeyboard(find.byType(TextField));
658
    await tester.testTextInput.receiveAction(TextInputAction.done);
659 660
    await tester.pump();
    await tester.showKeyboard(find.byType(TextField));
661
    await tester.testTextInput.receiveAction(TextInputAction.done);
662 663 664 665 666
    await tester.pump();
    await tester.showKeyboard(find.byType(TextField));
    await tester.showKeyboard(find.byType(TextField));
    await tester.pump();
  });
667

668
  testWidgets('verifyTickersWereDisposed control test', (WidgetTester tester) async {
669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
    FlutterError error;
    final Ticker ticker = tester.createTicker((Duration duration) {});
    ticker.start();
    try {
      tester.verifyTickersWereDisposed('');
    } on FlutterError catch (e) {
      error = e;
    } finally {
      expect(error, isNotNull);
      expect(error.diagnostics.length, 4);
      expect(error.diagnostics[2].level, DiagnosticLevel.hint);
      expect(
        error.diagnostics[2].toStringDeep(),
        'Tickers used by AnimationControllers should be disposed by\n'
        'calling dispose() on the AnimationController itself. Otherwise,\n'
        'the ticker will leak.\n',
      );
Dan Field's avatar
Dan Field committed
686
      expect(error.diagnostics.last, isA<DiagnosticsProperty<Ticker>>());
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 735 736 737 738 739 740 741 742 743
      expect(error.diagnostics.last.value, ticker);
      expect(error.toStringDeep(), startsWith(
        'FlutterError\n'
        '   A Ticker was active .\n'
        '   All Tickers must be disposed.\n'
        '   Tickers used by AnimationControllers should be disposed by\n'
        '   calling dispose() on the AnimationController itself. Otherwise,\n'
        '   the ticker will leak.\n'
        '   The offending ticker was:\n'
        '     _TestTicker()\n',
      ));
    }
    ticker.stop();
  });

  group('testWidgets variants work', () {
    int numberOfVariationsRun = 0;

    testWidgets('variant tests run all values provided', (WidgetTester tester) async {
      if (debugDefaultTargetPlatformOverride == null) {
        expect(numberOfVariationsRun, equals(TargetPlatform.values.length));
      } else {
        numberOfVariationsRun += 1;
      }
    }, variant: TargetPlatformVariant(TargetPlatform.values.toSet()));

    testWidgets('variant tests have descriptions with details', (WidgetTester tester) async {
      if (debugDefaultTargetPlatformOverride == null) {
        expect(tester.testDescription, equals('variant tests have descriptions with details'));
      } else {
        expect(tester.testDescription, equals('variant tests have descriptions with details ($debugDefaultTargetPlatformOverride)'));
      }
    }, variant: TargetPlatformVariant(TargetPlatform.values.toSet()));
  });

  group('TargetPlatformVariant', () {
    int numberOfVariationsRun = 0;
    TargetPlatform origTargetPlatform;

    setUpAll((){
      origTargetPlatform = debugDefaultTargetPlatformOverride;
    });

    tearDownAll((){
      expect(debugDefaultTargetPlatformOverride, equals(origTargetPlatform));
    });

    testWidgets('TargetPlatformVariant.only tests given value', (WidgetTester tester) async {
      expect(debugDefaultTargetPlatformOverride, equals(TargetPlatform.iOS));
      expect(defaultTargetPlatform, equals(TargetPlatform.iOS));
    }, variant: TargetPlatformVariant.only(TargetPlatform.iOS));

    testWidgets('TargetPlatformVariant.all tests run all variants', (WidgetTester tester) async {
      if (debugDefaultTargetPlatformOverride == null) {
        expect(numberOfVariationsRun, equals(TargetPlatform.values.length));
      } else {
        numberOfVariationsRun += 1;
744
      }
745
    }, variant: TargetPlatformVariant.all());
746
  });
747
}
748 749 750 751 752 753 754 755

class FakeMatcher extends AsyncMatcher {
  FakeMatcher(this.completer);

  final Completer<void> completer;

  @override
  Future<String> matchAsync(dynamic object) {
756
    return completer.future.then<String>((void value) {
757 758 759 760 761 762 763
      return object?.toString();
    });
  }

  @override
  Description describe(Description description) => description.add('--fake--');
}
764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788

class _SingleTickerTest extends StatefulWidget {
  const _SingleTickerTest({Key key}) : super(key: key);

  @override
  _SingleTickerTestState createState() => _SingleTickerTestState();
}

class _SingleTickerTestState extends State<_SingleTickerTest> with SingleTickerProviderStateMixin {
  AnimationController controller;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 100),
    )  ;
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}