routes_test.dart 14 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:collection';

7
import 'package:flutter/foundation.dart';
8
import 'package:mockito/mockito.dart';
9
import 'package:flutter_test/flutter_test.dart';
10
import 'package:flutter/widgets.dart';
11 12 13

final List<String> results = <String>[];

14
Set<TestRoute> routes = HashSet<TestRoute>();
15

16
class TestRoute extends Route<String> with LocalHistoryRoute<String> {
17 18 19
  TestRoute(this.name);
  final String name;

20
  @override
21 22
  List<OverlayEntry> get overlayEntries => _entries;

23
  final List<OverlayEntry> _entries = <OverlayEntry>[];
24 25 26 27 28

  void log(String s) {
    results.add('$name: $s');
  }

29
  @override
Hixie's avatar
Hixie committed
30
  void install(OverlayEntry insertionPoint) {
31
    log('install');
32 33
    final OverlayEntry entry = OverlayEntry(
      builder: (BuildContext context) => Container(),
34
      opaque: true,
35 36
    );
    _entries.add(entry);
Hixie's avatar
Hixie committed
37
    navigator.overlay?.insert(entry, above: insertionPoint);
38
    routes.add(this);
39
    super.install(insertionPoint);
40 41
  }

42
  @override
43
  TickerFuture didPush() {
44
    log('didPush');
45
    return super.didPush();
46 47
  }

48
  @override
49
  void didReplace(Route<dynamic> oldRoute) {
50
    expect(oldRoute, isInstanceOf<TestRoute>());
51 52 53
    final TestRoute castRoute = oldRoute;
    log('didReplace ${castRoute.name}');
    super.didReplace(castRoute);
54 55
  }

56
  @override
57 58
  bool didPop(String result) {
    log('didPop $result');
59 60
    bool returnValue;
    if (returnValue = super.didPop(result))
61
      navigator.finalizeRoute(this);
62
    return returnValue;
63 64
  }

65
  @override
66
  void didPopNext(Route<dynamic> nextRoute) {
67
    expect(nextRoute, isInstanceOf<TestRoute>());
68 69 70
    final TestRoute castRoute = nextRoute;
    log('didPopNext ${castRoute.name}');
    super.didPopNext(castRoute);
71 72
  }

73
  @override
74
  void didChangeNext(Route<dynamic> nextRoute) {
75
    expect(nextRoute, anyOf(isNull, isInstanceOf<TestRoute>()));
76 77 78
    final TestRoute castRoute = nextRoute;
    log('didChangeNext ${castRoute?.name}');
    super.didChangeNext(castRoute);
79 80
  }

81
  @override
82 83
  void dispose() {
    log('dispose');
84 85
    for (OverlayEntry entry in _entries)
      entry.remove();
86
    _entries.clear();
87
    routes.remove(this);
88
    super.dispose();
89 90 91 92
  }

}

93
Future<void> runNavigatorTest(
94 95
  WidgetTester tester,
  NavigatorState host,
96
  VoidCallback test,
97
  List<String> expectations,
98
) async {
99
  expect(host, isNotNull);
100
  test();
101 102
  expect(results, equals(expectations));
  results.clear();
103
  await tester.pump();
104 105 106
}

void main() {
107
  testWidgets('Route settings', (WidgetTester tester) async {
108
    const RouteSettings settings = RouteSettings(name: 'A');
109
    expect(settings, hasOneLineDescription);
110 111 112 113 114 115
    final RouteSettings settings2 = settings.copyWith(name: 'B');
    expect(settings2.name, 'B');
    expect(settings2.isInitialRoute, false);
    final RouteSettings settings3 = settings2.copyWith(isInitialRoute: true);
    expect(settings3.name, 'B');
    expect(settings3.isInitialRoute, true);
116 117
  });

118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
  testWidgets('Route settings arguments', (WidgetTester tester) async {
    const RouteSettings settings = RouteSettings(name: 'A');
    expect(settings.arguments, isNull);

    final Object arguments = Object();
    final RouteSettings settings2 = RouteSettings(name: 'A', arguments: arguments);
    expect(settings2.arguments, same(arguments));

    final RouteSettings settings3 = settings2.copyWith();
    expect(settings3.arguments, equals(arguments));

    final Object arguments2 = Object();
    final RouteSettings settings4 = settings2.copyWith(arguments: arguments2);
    expect(settings4.arguments, same(arguments2));
    expect(settings4.arguments, isNot(same(arguments)));
  });

135
  testWidgets('Route management - push, replace, pop', (WidgetTester tester) async {
136
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
137
    await tester.pumpWidget(
138
      Directionality(
139
        textDirection: TextDirection.ltr,
140
        child: Navigator(
141
          key: navigatorKey,
142
          onGenerateRoute: (_) => TestRoute('initial'),
143 144 145
        ),
      ),
    );
146
    final NavigatorState host = navigatorKey.currentState;
147
    await runNavigatorTest(
148 149
      tester,
      host,
150
      () { },
151
      <String>[
152 153 154
        'initial: install',
        'initial: didPush',
        'initial: didChangeNext null',
155
      ],
156 157
    );
    TestRoute second;
158
    await runNavigatorTest(
159 160
      tester,
      host,
161
      () { host.push(second = TestRoute('second')); },
162
      <String>[
163 164 165 166
        'second: install',
        'second: didPush',
        'second: didChangeNext null',
        'initial: didChangeNext second',
167
      ],
168
    );
169
    await runNavigatorTest(
170 171
      tester,
      host,
172
      () { host.push(TestRoute('third')); },
173
      <String>[
174 175 176 177
        'third: install',
        'third: didPush',
        'third: didChangeNext null',
        'second: didChangeNext third',
178
      ],
179
    );
180
    await runNavigatorTest(
181 182
      tester,
      host,
183
      () { host.replace(oldRoute: second, newRoute: TestRoute('two')); },
184
      <String>[
185 186 187 188 189
        'two: install',
        'two: didReplace second',
        'two: didChangeNext third',
        'initial: didChangeNext two',
        'second: dispose',
190
      ],
191
    );
192
    await runNavigatorTest(
193 194
      tester,
      host,
195
      () { host.pop('hello'); },
196
      <String>[
197 198 199
        'third: didPop hello',
        'third: dispose',
        'two: didPopNext third',
200
      ],
201
    );
202
    await runNavigatorTest(
203 204
      tester,
      host,
205
      () { host.pop('good bye'); },
206
      <String>[
207 208 209
        'two: didPop good bye',
        'two: dispose',
        'initial: didPopNext two',
210
      ],
211
    );
212
    await tester.pumpWidget(Container());
213
    expect(results, equals(<String>['initial: dispose']));
214 215
    expect(routes.isEmpty, isTrue);
    results.clear();
216 217
  });

218
  testWidgets('Route management - push, remove, pop', (WidgetTester tester) async {
219
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
220
    await tester.pumpWidget(
221
      Directionality(
222
        textDirection: TextDirection.ltr,
223
        child: Navigator(
224
          key: navigatorKey,
225
          onGenerateRoute: (_) => TestRoute('first'),
226 227 228
        ),
      ),
    );
229
    final NavigatorState host = navigatorKey.currentState;
230
    await runNavigatorTest(
231 232
      tester,
      host,
233
      () { },
234
      <String>[
235 236 237
        'first: install',
        'first: didPush',
        'first: didChangeNext null',
238
      ],
239 240
    );
    TestRoute second;
241
    await runNavigatorTest(
242 243
      tester,
      host,
244
      () { host.push(second = TestRoute('second')); },
245
      <String>[
246 247 248 249
        'second: install',
        'second: didPush',
        'second: didChangeNext null',
        'first: didChangeNext second',
250
      ],
251
    );
252
    await runNavigatorTest(
253 254
      tester,
      host,
255
      () { host.push(TestRoute('third')); },
256
      <String>[
257 258 259 260
        'third: install',
        'third: didPush',
        'third: didChangeNext null',
        'second: didChangeNext third',
261
      ],
262
    );
263
    await runNavigatorTest(
264 265
      tester,
      host,
266
      () { host.removeRouteBelow(second); },
267
      <String>[
268
        'first: dispose',
269
      ],
270
    );
271
    await runNavigatorTest(
272 273
      tester,
      host,
274
      () { host.pop('good bye'); },
275
      <String>[
276 277 278
        'third: didPop good bye',
        'third: dispose',
        'second: didPopNext third',
279
      ],
280
    );
281
    await runNavigatorTest(
282 283
      tester,
      host,
284
      () { host.push(TestRoute('three')); },
285
      <String>[
286 287 288 289
        'three: install',
        'three: didPush',
        'three: didChangeNext null',
        'second: didChangeNext three',
290
      ],
291 292
    );
    TestRoute four;
293
    await runNavigatorTest(
294 295
      tester,
      host,
296
      () { host.push(four = TestRoute('four')); },
297
      <String>[
298 299 300 301
        'four: install',
        'four: didPush',
        'four: didChangeNext null',
        'three: didChangeNext four',
302
      ],
303
    );
304
    await runNavigatorTest(
305 306
      tester,
      host,
307
      () { host.removeRouteBelow(four); },
308
      <String>[
309 310
        'second: didChangeNext four',
        'three: dispose',
311
      ],
312
    );
313
    await runNavigatorTest(
314 315
      tester,
      host,
316
      () { host.pop('the end'); },
317
      <String>[
318 319 320
        'four: didPop the end',
        'four: dispose',
        'second: didPopNext four',
321
      ],
322
    );
323
    await tester.pumpWidget(Container());
324
    expect(results, equals(<String>['second: dispose']));
325 326
    expect(routes.isEmpty, isTrue);
    results.clear();
327 328
  });

329
  testWidgets('Route management - push, replace, popUntil', (WidgetTester tester) async {
330
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
331
    await tester.pumpWidget(
332
      Directionality(
333
        textDirection: TextDirection.ltr,
334
        child: Navigator(
335
          key: navigatorKey,
336
          onGenerateRoute: (_) => TestRoute('A'),
337 338 339
        ),
      ),
    );
340
    final NavigatorState host = navigatorKey.currentState;
341
    await runNavigatorTest(
342 343
      tester,
      host,
344
      () { },
345
      <String>[
346 347 348
        'A: install',
        'A: didPush',
        'A: didChangeNext null',
349
      ],
350
    );
351
    await runNavigatorTest(
352 353
      tester,
      host,
354
      () { host.push(TestRoute('B')); },
355
      <String>[
356 357 358 359
        'B: install',
        'B: didPush',
        'B: didChangeNext null',
        'A: didChangeNext B',
360
      ],
361 362
    );
    TestRoute routeC;
363
    await runNavigatorTest(
364 365
      tester,
      host,
366
      () { host.push(routeC = TestRoute('C')); },
367
      <String>[
368 369 370 371
        'C: install',
        'C: didPush',
        'C: didChangeNext null',
        'B: didChangeNext C',
372
      ],
373
    );
374
    expect(routeC.isActive, isTrue);
375
    TestRoute routeB;
376
    await runNavigatorTest(
377 378
      tester,
      host,
379
      () { host.replaceRouteBelow(anchorRoute: routeC, newRoute: routeB = TestRoute('b')); },
380
      <String>[
381 382 383 384 385
        'b: install',
        'b: didReplace B',
        'b: didChangeNext C',
        'A: didChangeNext b',
        'B: dispose',
386
      ],
387
    );
388
    await runNavigatorTest(
389 390
      tester,
      host,
Hans Muller's avatar
Hans Muller committed
391
      () { host.popUntil((Route<dynamic> route) => route == routeB); },
392
      <String>[
393 394 395
        'C: didPop null',
        'C: dispose',
        'b: didPopNext C',
396
      ],
397
    );
398
    await tester.pumpWidget(Container());
399
    expect(results, equals(<String>['A: dispose', 'b: dispose']));
400 401
    expect(routes.isEmpty, isTrue);
    results.clear();
402
  });
Hans Muller's avatar
Hans Muller committed
403 404

  testWidgets('Route localHistory - popUntil', (WidgetTester tester) async {
405 406
    final TestRoute routeA = TestRoute('A');
    routeA.addLocalHistoryEntry(LocalHistoryEntry(
Hans Muller's avatar
Hans Muller committed
407 408
      onRemove: () { routeA.log('onRemove 0'); }
    ));
409
    routeA.addLocalHistoryEntry(LocalHistoryEntry(
Hans Muller's avatar
Hans Muller committed
410 411
      onRemove: () { routeA.log('onRemove 1'); }
    ));
412
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
413
    await tester.pumpWidget(
414
      Directionality(
415
        textDirection: TextDirection.ltr,
416
        child: Navigator(
417
          key: navigatorKey,
418
          onGenerateRoute: (_) => routeA,
419 420 421
        ),
      ),
    );
422
    final NavigatorState host = navigatorKey.currentState;
Hans Muller's avatar
Hans Muller committed
423 424 425 426 427 428 429 430 431 432 433 434
    await runNavigatorTest(
      tester,
      host,
      () { host.popUntil((Route<dynamic> route) => !route.willHandlePopInternally); },
      <String>[
        'A: install',
        'A: didPush',
        'A: didChangeNext null',
        'A: didPop null',
        'A: onRemove 1',
        'A: didPop null',
        'A: onRemove 0',
435
      ],
Hans Muller's avatar
Hans Muller committed
436 437 438 439 440 441 442
    );

    await runNavigatorTest(
      tester,
      host,
      () { host.popUntil((Route<dynamic> route) => !route.willHandlePopInternally); },
      <String>[
443
      ],
Hans Muller's avatar
Hans Muller committed
444 445
    );
  });
446 447 448

  group('PageRouteObserver', () {
    test('calls correct listeners', () {
449 450 451
      final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>();
      final RouteAware pageRouteAware1 = MockRouteAware();
      final MockPageRoute route1 = MockPageRoute();
452 453 454
      observer.subscribe(pageRouteAware1, route1);
      verify(pageRouteAware1.didPush()).called(1);

455 456
      final RouteAware pageRouteAware2 = MockRouteAware();
      final MockPageRoute route2 = MockPageRoute();
457 458 459 460 461 462 463 464 465 466 467 468
      observer.didPush(route2, route1);
      verify(pageRouteAware1.didPushNext()).called(1);

      observer.subscribe(pageRouteAware2, route2);
      verify(pageRouteAware2.didPush()).called(1);

      observer.didPop(route2, route1);
      verify(pageRouteAware2.didPop()).called(1);
      verify(pageRouteAware1.didPopNext()).called(1);
    });

    test('does not call listeners for non-PageRoute', () {
469 470 471 472
      final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>();
      final RouteAware pageRouteAware = MockRouteAware();
      final MockPageRoute pageRoute = MockPageRoute();
      final MockRoute route = MockRoute();
473 474 475 476 477 478 479
      observer.subscribe(pageRouteAware, pageRoute);
      verify(pageRouteAware.didPush());

      observer.didPush(route, pageRoute);
      observer.didPop(route, pageRoute);
      verifyNoMoreInteractions(pageRouteAware);
    });
480 481

    test('does not call listeners when already subscribed', () {
482 483 484
      final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>();
      final RouteAware pageRouteAware = MockRouteAware();
      final MockPageRoute pageRoute = MockPageRoute();
485 486 487 488 489 490
      observer.subscribe(pageRouteAware, pageRoute);
      observer.subscribe(pageRouteAware, pageRoute);
      verify(pageRouteAware.didPush()).called(1);
    });

    test('does not call listeners when unsubscribed', () {
491 492 493 494
      final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>();
      final RouteAware pageRouteAware = MockRouteAware();
      final MockPageRoute pageRoute = MockPageRoute();
      final MockPageRoute nextPageRoute = MockPageRoute();
495 496 497 498 499 500 501 502 503 504
      observer.subscribe(pageRouteAware, pageRoute);
      observer.subscribe(pageRouteAware, nextPageRoute);
      verify(pageRouteAware.didPush()).called(2);

      observer.unsubscribe(pageRouteAware);

      observer.didPush(nextPageRoute, pageRoute);
      observer.didPop(nextPageRoute, pageRoute);
      verifyNoMoreInteractions(pageRouteAware);
    });
505
  });
506
}
507 508 509 510 511 512

class MockPageRoute extends Mock implements PageRoute<dynamic> { }

class MockRoute extends Mock implements Route<dynamic> { }

class MockRouteAware extends Mock implements RouteAware { }