| // 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. |
| |
| import 'dart:collection'; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:mockito/mockito.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| final List<String> results = <String>[]; |
| |
| Set<TestRoute> routes = HashSet<TestRoute>(); |
| |
| class TestRoute extends Route<String> with LocalHistoryRoute<String> { |
| TestRoute(this.name); |
| final String name; |
| |
| @override |
| List<OverlayEntry> get overlayEntries => _entries; |
| |
| final List<OverlayEntry> _entries = <OverlayEntry>[]; |
| |
| void log(String s) { |
| results.add('$name: $s'); |
| } |
| |
| @override |
| void install(OverlayEntry insertionPoint) { |
| log('install'); |
| final OverlayEntry entry = OverlayEntry( |
| builder: (BuildContext context) => Container(), |
| opaque: true, |
| ); |
| _entries.add(entry); |
| navigator.overlay?.insert(entry, above: insertionPoint); |
| routes.add(this); |
| super.install(insertionPoint); |
| } |
| |
| @override |
| TickerFuture didPush() { |
| log('didPush'); |
| return super.didPush(); |
| } |
| |
| @override |
| void didReplace(Route<dynamic> oldRoute) { |
| expect(oldRoute, isInstanceOf<TestRoute>()); |
| final TestRoute castRoute = oldRoute; |
| log('didReplace ${castRoute.name}'); |
| super.didReplace(castRoute); |
| } |
| |
| @override |
| bool didPop(String result) { |
| log('didPop $result'); |
| bool returnValue; |
| if (returnValue = super.didPop(result)) |
| navigator.finalizeRoute(this); |
| return returnValue; |
| } |
| |
| @override |
| void didPopNext(Route<dynamic> nextRoute) { |
| expect(nextRoute, isInstanceOf<TestRoute>()); |
| final TestRoute castRoute = nextRoute; |
| log('didPopNext ${castRoute.name}'); |
| super.didPopNext(castRoute); |
| } |
| |
| @override |
| void didChangeNext(Route<dynamic> nextRoute) { |
| expect(nextRoute, anyOf(isNull, isInstanceOf<TestRoute>())); |
| final TestRoute castRoute = nextRoute; |
| log('didChangeNext ${castRoute?.name}'); |
| super.didChangeNext(castRoute); |
| } |
| |
| @override |
| void dispose() { |
| log('dispose'); |
| for (OverlayEntry entry in _entries) |
| entry.remove(); |
| _entries.clear(); |
| routes.remove(this); |
| super.dispose(); |
| } |
| |
| } |
| |
| Future<void> runNavigatorTest( |
| WidgetTester tester, |
| NavigatorState host, |
| VoidCallback test, |
| List<String> expectations, |
| ) async { |
| expect(host, isNotNull); |
| test(); |
| expect(results, equals(expectations)); |
| results.clear(); |
| await tester.pump(); |
| } |
| |
| void main() { |
| testWidgets('Route settings', (WidgetTester tester) async { |
| const RouteSettings settings = RouteSettings(name: 'A'); |
| expect(settings, hasOneLineDescription); |
| 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); |
| }); |
| |
| 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))); |
| }); |
| |
| testWidgets('Route management - push, replace, pop', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Navigator( |
| key: navigatorKey, |
| onGenerateRoute: (_) => TestRoute('initial'), |
| ), |
| ), |
| ); |
| final NavigatorState host = navigatorKey.currentState; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { }, |
| <String>[ |
| 'initial: install', |
| 'initial: didPush', |
| 'initial: didChangeNext null', |
| ], |
| ); |
| TestRoute second; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(second = TestRoute('second')); }, |
| <String>[ |
| 'second: install', |
| 'second: didPush', |
| 'second: didChangeNext null', |
| 'initial: didChangeNext second', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(TestRoute('third')); }, |
| <String>[ |
| 'third: install', |
| 'third: didPush', |
| 'third: didChangeNext null', |
| 'second: didChangeNext third', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.replace(oldRoute: second, newRoute: TestRoute('two')); }, |
| <String>[ |
| 'two: install', |
| 'two: didReplace second', |
| 'two: didChangeNext third', |
| 'initial: didChangeNext two', |
| 'second: dispose', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.pop('hello'); }, |
| <String>[ |
| 'third: didPop hello', |
| 'third: dispose', |
| 'two: didPopNext third', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.pop('good bye'); }, |
| <String>[ |
| 'two: didPop good bye', |
| 'two: dispose', |
| 'initial: didPopNext two', |
| ], |
| ); |
| await tester.pumpWidget(Container()); |
| expect(results, equals(<String>['initial: dispose'])); |
| expect(routes.isEmpty, isTrue); |
| results.clear(); |
| }); |
| |
| testWidgets('Route management - push, remove, pop', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Navigator( |
| key: navigatorKey, |
| onGenerateRoute: (_) => TestRoute('first'), |
| ), |
| ), |
| ); |
| final NavigatorState host = navigatorKey.currentState; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { }, |
| <String>[ |
| 'first: install', |
| 'first: didPush', |
| 'first: didChangeNext null', |
| ], |
| ); |
| TestRoute second; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(second = TestRoute('second')); }, |
| <String>[ |
| 'second: install', |
| 'second: didPush', |
| 'second: didChangeNext null', |
| 'first: didChangeNext second', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(TestRoute('third')); }, |
| <String>[ |
| 'third: install', |
| 'third: didPush', |
| 'third: didChangeNext null', |
| 'second: didChangeNext third', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.removeRouteBelow(second); }, |
| <String>[ |
| 'first: dispose', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.pop('good bye'); }, |
| <String>[ |
| 'third: didPop good bye', |
| 'third: dispose', |
| 'second: didPopNext third', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(TestRoute('three')); }, |
| <String>[ |
| 'three: install', |
| 'three: didPush', |
| 'three: didChangeNext null', |
| 'second: didChangeNext three', |
| ], |
| ); |
| TestRoute four; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(four = TestRoute('four')); }, |
| <String>[ |
| 'four: install', |
| 'four: didPush', |
| 'four: didChangeNext null', |
| 'three: didChangeNext four', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.removeRouteBelow(four); }, |
| <String>[ |
| 'second: didChangeNext four', |
| 'three: dispose', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.pop('the end'); }, |
| <String>[ |
| 'four: didPop the end', |
| 'four: dispose', |
| 'second: didPopNext four', |
| ], |
| ); |
| await tester.pumpWidget(Container()); |
| expect(results, equals(<String>['second: dispose'])); |
| expect(routes.isEmpty, isTrue); |
| results.clear(); |
| }); |
| |
| testWidgets('Route management - push, replace, popUntil', (WidgetTester tester) async { |
| final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Navigator( |
| key: navigatorKey, |
| onGenerateRoute: (_) => TestRoute('A'), |
| ), |
| ), |
| ); |
| final NavigatorState host = navigatorKey.currentState; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { }, |
| <String>[ |
| 'A: install', |
| 'A: didPush', |
| 'A: didChangeNext null', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(TestRoute('B')); }, |
| <String>[ |
| 'B: install', |
| 'B: didPush', |
| 'B: didChangeNext null', |
| 'A: didChangeNext B', |
| ], |
| ); |
| TestRoute routeC; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.push(routeC = TestRoute('C')); }, |
| <String>[ |
| 'C: install', |
| 'C: didPush', |
| 'C: didChangeNext null', |
| 'B: didChangeNext C', |
| ], |
| ); |
| expect(routeC.isActive, isTrue); |
| TestRoute routeB; |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.replaceRouteBelow(anchorRoute: routeC, newRoute: routeB = TestRoute('b')); }, |
| <String>[ |
| 'b: install', |
| 'b: didReplace B', |
| 'b: didChangeNext C', |
| 'A: didChangeNext b', |
| 'B: dispose', |
| ], |
| ); |
| await runNavigatorTest( |
| tester, |
| host, |
| () { host.popUntil((Route<dynamic> route) => route == routeB); }, |
| <String>[ |
| 'C: didPop null', |
| 'C: dispose', |
| 'b: didPopNext C', |
| ], |
| ); |
| await tester.pumpWidget(Container()); |
| expect(results, equals(<String>['A: dispose', 'b: dispose'])); |
| expect(routes.isEmpty, isTrue); |
| results.clear(); |
| }); |
| |
| testWidgets('Route localHistory - popUntil', (WidgetTester tester) async { |
| final TestRoute routeA = TestRoute('A'); |
| routeA.addLocalHistoryEntry(LocalHistoryEntry( |
| onRemove: () { routeA.log('onRemove 0'); } |
| )); |
| routeA.addLocalHistoryEntry(LocalHistoryEntry( |
| onRemove: () { routeA.log('onRemove 1'); } |
| )); |
| final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>(); |
| await tester.pumpWidget( |
| Directionality( |
| textDirection: TextDirection.ltr, |
| child: Navigator( |
| key: navigatorKey, |
| onGenerateRoute: (_) => routeA, |
| ), |
| ), |
| ); |
| final NavigatorState host = navigatorKey.currentState; |
| 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>[ |
| ], |
| ); |
| }); |
| |
| group('PageRouteObserver', () { |
| test('calls correct listeners', () { |
| final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>(); |
| final RouteAware pageRouteAware1 = MockRouteAware(); |
| final MockPageRoute route1 = MockPageRoute(); |
| observer.subscribe(pageRouteAware1, route1); |
| verify(pageRouteAware1.didPush()).called(1); |
| |
| final RouteAware pageRouteAware2 = MockRouteAware(); |
| final MockPageRoute route2 = MockPageRoute(); |
| 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', () { |
| final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>(); |
| final RouteAware pageRouteAware = MockRouteAware(); |
| final MockPageRoute pageRoute = MockPageRoute(); |
| final MockRoute route = MockRoute(); |
| observer.subscribe(pageRouteAware, pageRoute); |
| verify(pageRouteAware.didPush()); |
| |
| observer.didPush(route, pageRoute); |
| observer.didPop(route, pageRoute); |
| verifyNoMoreInteractions(pageRouteAware); |
| }); |
| |
| test('does not call listeners when already subscribed', () { |
| final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>(); |
| final RouteAware pageRouteAware = MockRouteAware(); |
| final MockPageRoute pageRoute = MockPageRoute(); |
| observer.subscribe(pageRouteAware, pageRoute); |
| observer.subscribe(pageRouteAware, pageRoute); |
| verify(pageRouteAware.didPush()).called(1); |
| }); |
| |
| test('does not call listeners when unsubscribed', () { |
| final RouteObserver<PageRoute<dynamic>> observer = RouteObserver<PageRoute<dynamic>>(); |
| final RouteAware pageRouteAware = MockRouteAware(); |
| final MockPageRoute pageRoute = MockPageRoute(); |
| final MockPageRoute nextPageRoute = MockPageRoute(); |
| 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); |
| }); |
| }); |
| } |
| |
| class MockPageRoute extends Mock implements PageRoute<dynamic> { } |
| |
| class MockRoute extends Mock implements Route<dynamic> { } |
| |
| class MockRouteAware extends Mock implements RouteAware { } |