routes_test.dart 13.3 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:mockito/mockito.dart';
8
import 'package:flutter_test/flutter_test.dart';
9
import 'package:flutter/widgets.dart';
10 11 12

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

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

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

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

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

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

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

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

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

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

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

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

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

}

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

void main() {
106
  testWidgets('Route settings', (WidgetTester tester) async {
107
    const RouteSettings settings = RouteSettings(name: 'A');
108
    expect(settings, hasOneLineDescription);
109 110 111 112 113 114
    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);
115 116
  });

117
  testWidgets('Route management - push, replace, pop', (WidgetTester tester) async {
118
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
119
    await tester.pumpWidget(
120
      Directionality(
121
        textDirection: TextDirection.ltr,
122
        child: Navigator(
123
          key: navigatorKey,
124
          onGenerateRoute: (_) => TestRoute('initial'),
125 126 127
        ),
      ),
    );
128
    final NavigatorState host = navigatorKey.currentState;
129
    await runNavigatorTest(
130 131
      tester,
      host,
132
      () { },
133
      <String>[
134 135 136 137 138 139
        'initial: install',
        'initial: didPush',
        'initial: didChangeNext null',
      ]
    );
    TestRoute second;
140
    await runNavigatorTest(
141 142
      tester,
      host,
143
      () { host.push(second = TestRoute('second')); },
144
      <String>[
145 146 147 148 149 150
        'second: install',
        'second: didPush',
        'second: didChangeNext null',
        'initial: didChangeNext second',
      ]
    );
151
    await runNavigatorTest(
152 153
      tester,
      host,
154
      () { host.push(TestRoute('third')); },
155
      <String>[
156 157 158 159 160 161
        'third: install',
        'third: didPush',
        'third: didChangeNext null',
        'second: didChangeNext third',
      ]
    );
162
    await runNavigatorTest(
163 164
      tester,
      host,
165
      () { host.replace(oldRoute: second, newRoute: TestRoute('two')); },
166
      <String>[
167 168 169 170 171 172 173
        'two: install',
        'two: didReplace second',
        'two: didChangeNext third',
        'initial: didChangeNext two',
        'second: dispose',
      ]
    );
174
    await runNavigatorTest(
175 176
      tester,
      host,
177
      () { host.pop('hello'); },
178
      <String>[
179 180 181 182 183
        'third: didPop hello',
        'third: dispose',
        'two: didPopNext third',
      ]
    );
184
    await runNavigatorTest(
185 186
      tester,
      host,
187
      () { host.pop('good bye'); },
188
      <String>[
189 190 191 192 193
        'two: didPop good bye',
        'two: dispose',
        'initial: didPopNext two',
      ]
    );
194
    await tester.pumpWidget(Container());
195
    expect(results, equals(<String>['initial: dispose']));
196 197
    expect(routes.isEmpty, isTrue);
    results.clear();
198 199
  });

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

311
  testWidgets('Route management - push, replace, popUntil', (WidgetTester tester) async {
312
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
313
    await tester.pumpWidget(
314
      Directionality(
315
        textDirection: TextDirection.ltr,
316
        child: Navigator(
317
          key: navigatorKey,
318
          onGenerateRoute: (_) => TestRoute('A')
319 320 321
        ),
      ),
    );
322
    final NavigatorState host = navigatorKey.currentState;
323
    await runNavigatorTest(
324 325
      tester,
      host,
326
      () { },
327
      <String>[
328 329 330 331 332
        'A: install',
        'A: didPush',
        'A: didChangeNext null',
      ]
    );
333
    await runNavigatorTest(
334 335
      tester,
      host,
336
      () { host.push(TestRoute('B')); },
337
      <String>[
338 339 340 341 342 343 344
        'B: install',
        'B: didPush',
        'B: didChangeNext null',
        'A: didChangeNext B',
      ]
    );
    TestRoute routeC;
345
    await runNavigatorTest(
346 347
      tester,
      host,
348
      () { host.push(routeC = TestRoute('C')); },
349
      <String>[
350 351 352 353 354 355
        'C: install',
        'C: didPush',
        'C: didChangeNext null',
        'B: didChangeNext C',
      ]
    );
356
    expect(routeC.isActive, isTrue);
357
    TestRoute routeB;
358
    await runNavigatorTest(
359 360
      tester,
      host,
361
      () { host.replaceRouteBelow(anchorRoute: routeC, newRoute: routeB = TestRoute('b')); },
362
      <String>[
363 364 365 366 367 368 369
        'b: install',
        'b: didReplace B',
        'b: didChangeNext C',
        'A: didChangeNext b',
        'B: dispose',
      ]
    );
370
    await runNavigatorTest(
371 372
      tester,
      host,
Hans Muller's avatar
Hans Muller committed
373
      () { host.popUntil((Route<dynamic> route) => route == routeB); },
374
      <String>[
375 376 377 378 379
        'C: didPop null',
        'C: dispose',
        'b: didPopNext C',
      ]
    );
380
    await tester.pumpWidget(Container());
381
    expect(results, equals(<String>['A: dispose', 'b: dispose']));
382 383
    expect(routes.isEmpty, isTrue);
    results.clear();
384
  });
Hans Muller's avatar
Hans Muller committed
385 386

  testWidgets('Route localHistory - popUntil', (WidgetTester tester) async {
387 388
    final TestRoute routeA = TestRoute('A');
    routeA.addLocalHistoryEntry(LocalHistoryEntry(
Hans Muller's avatar
Hans Muller committed
389 390
      onRemove: () { routeA.log('onRemove 0'); }
    ));
391
    routeA.addLocalHistoryEntry(LocalHistoryEntry(
Hans Muller's avatar
Hans Muller committed
392 393
      onRemove: () { routeA.log('onRemove 1'); }
    ));
394
    final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
395
    await tester.pumpWidget(
396
      Directionality(
397
        textDirection: TextDirection.ltr,
398
        child: Navigator(
399 400 401 402 403
          key: navigatorKey,
          onGenerateRoute: (_) => routeA
        ),
      ),
    );
404
    final NavigatorState host = navigatorKey.currentState;
Hans Muller's avatar
Hans Muller committed
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427
    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',
      ]
    );

    await runNavigatorTest(
      tester,
      host,
      () { host.popUntil((Route<dynamic> route) => !route.willHandlePopInternally); },
      <String>[
      ]
    );
  });
428 429 430

  group('PageRouteObserver', () {
    test('calls correct listeners', () {
431 432 433
      final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>();
      final RouteAware pageRouteAware1 = MockRouteAware();
      final MockPageRoute route1 = MockPageRoute();
434 435 436
      observer.subscribe(pageRouteAware1, route1);
      verify(pageRouteAware1.didPush()).called(1);

437 438
      final RouteAware pageRouteAware2 = MockRouteAware();
      final MockPageRoute route2 = MockPageRoute();
439 440 441 442 443 444 445 446 447 448 449 450
      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', () {
451 452 453 454
      final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>();
      final RouteAware pageRouteAware = MockRouteAware();
      final MockPageRoute pageRoute = MockPageRoute();
      final MockRoute route = MockRoute();
455 456 457 458 459 460 461
      observer.subscribe(pageRouteAware, pageRoute);
      verify(pageRouteAware.didPush());

      observer.didPush(route, pageRoute);
      observer.didPop(route, pageRoute);
      verifyNoMoreInteractions(pageRouteAware);
    });
462 463

    test('does not call listeners when already subscribed', () {
464 465 466
      final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>();
      final RouteAware pageRouteAware = MockRouteAware();
      final MockPageRoute pageRoute = MockPageRoute();
467 468 469 470 471 472
      observer.subscribe(pageRouteAware, pageRoute);
      observer.subscribe(pageRouteAware, pageRoute);
      verify(pageRouteAware.didPush()).called(1);
    });

    test('does not call listeners when unsubscribed', () {
473 474 475 476
      final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>();
      final RouteAware pageRouteAware = MockRouteAware();
      final MockPageRoute pageRoute = MockPageRoute();
      final MockPageRoute nextPageRoute = MockPageRoute();
477 478 479 480 481 482 483 484 485 486
      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);
    });
487
  });
488
}
489 490 491 492 493 494

class MockPageRoute extends Mock implements PageRoute<dynamic> { }

class MockRoute extends Mock implements Route<dynamic> { }

class MockRouteAware extends Mock implements RouteAware { }