// 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 'package:flutter_test/flutter_test.dart' hide TypeMatcher; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; class TestTransition extends AnimatedWidget { const TestTransition({ Key key, this.childFirstHalf, this.childSecondHalf, Animation<double> animation }) : super(key: key, listenable: animation); final Widget childFirstHalf; final Widget childSecondHalf; @override Widget build(BuildContext context) { final Animation<double> animation = listenable; if (animation.value >= 0.5) return childSecondHalf; return childFirstHalf; } } class TestRoute<T> extends PageRoute<T> { TestRoute({ this.child, RouteSettings settings, this.barrierColor }) : super(settings: settings); final Widget child; @override Duration get transitionDuration => const Duration(milliseconds: 150); @override final Color barrierColor; @override bool get maintainState => false; @override Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return child; } } void main() { final Duration kTwoTenthsOfTheTransitionDuration = const Duration(milliseconds: 30); final Duration kFourTenthsOfTheTransitionDuration = const Duration(milliseconds: 60); testWidgets('Check onstage/offstage handling around transitions', (WidgetTester tester) async { final GlobalKey insideKey = new GlobalKey(); String state({ bool skipOffstage: true }) { String result = ''; if (tester.any(find.text('A', skipOffstage: skipOffstage))) result += 'A'; if (tester.any(find.text('B', skipOffstage: skipOffstage))) result += 'B'; if (tester.any(find.text('C', skipOffstage: skipOffstage))) result += 'C'; if (tester.any(find.text('D', skipOffstage: skipOffstage))) result += 'D'; if (tester.any(find.text('E', skipOffstage: skipOffstage))) result += 'E'; if (tester.any(find.text('F', skipOffstage: skipOffstage))) result += 'F'; if (tester.any(find.text('G', skipOffstage: skipOffstage))) result += 'G'; return result; } await tester.pumpWidget( new MaterialApp( onGenerateRoute: (RouteSettings settings) { switch (settings.name) { case '/': return new TestRoute<Null>( settings: settings, child: new Builder( key: insideKey, builder: (BuildContext context) { final PageRoute<Null> route = ModalRoute.of(context); return new Column( children: <Widget>[ new TestTransition( childFirstHalf: const Text('A'), childSecondHalf: const Text('B'), animation: route.animation ), new TestTransition( childFirstHalf: const Text('C'), childSecondHalf: const Text('D'), animation: route.secondaryAnimation ), ] ); } ) ); case '/2': return new TestRoute<Null>(settings: settings, child: const Text('E')); case '/3': return new TestRoute<Null>(settings: settings, child: const Text('F')); case '/4': return new TestRoute<Null>(settings: settings, child: const Text('G')); } } ) ); final NavigatorState navigator = insideKey.currentContext.ancestorStateOfType(const TypeMatcher<NavigatorState>()); expect(state(), equals('BC')); // transition ->1 is at 1.0 navigator.pushNamed('/2'); expect(state(), equals('BC')); // transition 1->2 is not yet built await tester.pump(); expect(state(), equals('BC')); // transition 1->2 is at 0.0 expect(state(skipOffstage: false), equals('BCE')); // E is offstage await tester.pump(kFourTenthsOfTheTransitionDuration); expect(state(), equals('BCE')); // transition 1->2 is at 0.4 await tester.pump(kFourTenthsOfTheTransitionDuration); expect(state(), equals('BDE')); // transition 1->2 is at 0.8 await tester.pump(kFourTenthsOfTheTransitionDuration); expect(state(), equals('E')); // transition 1->2 is at 1.0 expect(state(skipOffstage: false), equals('E')); // B and C are gone, the route is inactive with maintainState=false navigator.pop(); expect(state(), equals('E')); // transition 1<-2 is at 1.0, just reversed await tester.pump(); await tester.pump(); expect(state(), equals('BDE')); // transition 1<-2 is at 1.0 await tester.pump(kFourTenthsOfTheTransitionDuration); expect(state(), equals('BDE')); // transition 1<-2 is at 0.6 navigator.pushNamed('/3'); expect(state(), equals('BDE')); // transition 1<-2 is at 0.6 await tester.pump(); expect(state(), equals('BDE')); // transition 1<-2 is at 0.6, 1->3 is at 0.0 expect(state(skipOffstage: false), equals('BDEF')); // F is offstage since we're at 0.0 await tester.pump(kFourTenthsOfTheTransitionDuration); expect(state(), equals('BCEF')); // transition 1<-2 is at 0.2, 1->3 is at 0.4 expect(state(skipOffstage: false), equals('BCEF')); // nothing secret going on here await tester.pump(kFourTenthsOfTheTransitionDuration); expect(state(), equals('BDF')); // transition 1<-2 is done, 1->3 is at 0.8 navigator.pop(); expect(state(), equals('BDF')); // transition 1<-3 is at 0.8, just reversed await tester.pump(); expect(state(), equals('BDF')); // transition 1<-3 is at 0.8 await tester.pump(kTwoTenthsOfTheTransitionDuration); // notice that dT=0.2 here, not 0.4 expect(state(), equals('BDF')); // transition 1<-3 is at 0.6 await tester.pump(kFourTenthsOfTheTransitionDuration); expect(state(), equals('BCF')); // transition 1<-3 is at 0.2 navigator.pushNamed('/4'); expect(state(), equals('BCF')); // transition 1<-3 is at 0.2, 1->4 is not yet built await tester.pump(); expect(state(), equals('BCF')); // transition 1<-3 is at 0.2, 1->4 is at 0.0 expect(state(skipOffstage: false), equals('BCFG')); // G is offstage await tester.pump(kFourTenthsOfTheTransitionDuration); expect(state(), equals('BCG')); // transition 1<-3 is done, 1->4 is at 0.4 await tester.pump(kFourTenthsOfTheTransitionDuration); expect(state(), equals('BDG')); // transition 1->4 is at 0.8 await tester.pump(kFourTenthsOfTheTransitionDuration); expect(state(), equals('G')); // transition 1->4 is done expect(state(skipOffstage: false), equals('G')); // route 1 is not around any more }); testWidgets('Check onstage/offstage handling of barriers around transitions', (WidgetTester tester) async { await tester.pumpWidget( new MaterialApp( onGenerateRoute: (RouteSettings settings) { switch (settings.name) { case '/': return new TestRoute<Null>(settings: settings, child: const Text('A')); case '/1': return new TestRoute<Null>(settings: settings, barrierColor: const Color(0xFFFFFF00), child: const Text('B')); } } ) ); expect(find.byType(ModalBarrier), findsOneWidget); tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/1'); expect(find.byType(ModalBarrier), findsOneWidget); await tester.pump(); expect(find.byType(ModalBarrier), findsNWidgets(2)); expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).first).color, isNull); expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier).last).color, isNull); await tester.pump(const Duration(seconds: 1)); expect(find.byType(ModalBarrier), findsOneWidget); expect(tester.widget<ModalBarrier>(find.byType(ModalBarrier)).color, const Color(0xFFFFFF00)); }); }