page_forward_transitions_test.dart 8.1 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Hixie's avatar
Hixie committed
2 3 4 5
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
6
import 'package:flutter_test/flutter_test.dart';
Hixie's avatar
Hixie committed
7

8
class TestTransition extends AnimatedWidget {
9
  const TestTransition({
10
    super.key,
11 12 13
    required this.childFirstHalf,
    required this.childSecondHalf,
    required Animation<double> animation,
14
  }) : super(listenable: animation);
Hixie's avatar
Hixie committed
15 16 17 18

  final Widget childFirstHalf;
  final Widget childSecondHalf;

19
  @override
Hixie's avatar
Hixie committed
20
  Widget build(BuildContext context) {
21
    final Animation<double> animation = listenable as Animation<double>;
22
    if (animation.value >= 0.5) {
Hixie's avatar
Hixie committed
23
      return childSecondHalf;
24
    }
Hixie's avatar
Hixie committed
25 26 27 28
    return childFirstHalf;
  }
}

29
class TestRoute<T> extends PageRoute<T> {
30 31 32 33 34
  TestRoute({
    required this.child,
    required RouteSettings settings,
    this.barrierColor,
  }) : super(settings: settings);
35

36
  final Widget child;
37 38

  @override
39
  Duration get transitionDuration => const Duration(milliseconds: 150);
40 41

  @override
42
  final Color? barrierColor;
43

44
  @override
45
  String? get barrierLabel => null;
46

47 48 49
  @override
  bool get maintainState => false;

50
  @override
51
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
52 53
    return child;
  }
54 55
}

Hixie's avatar
Hixie committed
56
void main() {
57 58
  const Duration kTwoTenthsOfTheTransitionDuration = Duration(milliseconds: 30);
  const Duration kFourTenthsOfTheTransitionDuration = Duration(milliseconds: 60);
Hixie's avatar
Hixie committed
59

60
  testWidgets('Check onstage/offstage handling around transitions', (WidgetTester tester) async {
61

62
    final GlobalKey insideKey = GlobalKey();
63

64
    String state({ bool skipOffstage = true }) {
65
      String result = '';
66
      if (tester.any(find.text('A', skipOffstage: skipOffstage))) {
67
        result += 'A';
68 69
      }
      if (tester.any(find.text('B', skipOffstage: skipOffstage))) {
70
        result += 'B';
71 72
      }
      if (tester.any(find.text('C', skipOffstage: skipOffstage))) {
73
        result += 'C';
74 75
      }
      if (tester.any(find.text('D', skipOffstage: skipOffstage))) {
76
        result += 'D';
77 78
      }
      if (tester.any(find.text('E', skipOffstage: skipOffstage))) {
79
        result += 'E';
80 81
      }
      if (tester.any(find.text('F', skipOffstage: skipOffstage))) {
82
        result += 'F';
83 84
      }
      if (tester.any(find.text('G', skipOffstage: skipOffstage))) {
85
        result += 'G';
86
      }
87 88 89
      return result;
    }

90
    await tester.pumpWidget(
91
      MaterialApp(
92 93 94
        onGenerateRoute: (RouteSettings settings) {
          switch (settings.name) {
            case '/':
95
              return TestRoute<void>(
96
                settings: settings,
97
                child: Builder(
98 99
                  key: insideKey,
                  builder: (BuildContext context) {
100
                    final PageRoute<void> route = ModalRoute.of(context)! as PageRoute<void>;
101
                    return Column(
102
                      children: <Widget>[
103
                        TestTransition(
104 105
                          childFirstHalf: const Text('A'),
                          childSecondHalf: const Text('B'),
106
                          animation: route.animation!,
107
                        ),
108
                        TestTransition(
109 110
                          childFirstHalf: const Text('C'),
                          childSecondHalf: const Text('D'),
111
                          animation: route.secondaryAnimation!,
112
                        ),
113
                      ],
114
                    );
115 116
                  },
                ),
117
              );
118 119 120
            case '/2': return TestRoute<void>(settings: settings, child: const Text('E'));
            case '/3': return TestRoute<void>(settings: settings, child: const Text('F'));
            case '/4': return TestRoute<void>(settings: settings, child: const Text('G'));
Hixie's avatar
Hixie committed
121
          }
122
          return null;
123 124
        },
      ),
125
    );
Hixie's avatar
Hixie committed
126

127
    final NavigatorState navigator = insideKey.currentContext!.findAncestorStateOfType<NavigatorState>()!;
Hixie's avatar
Hixie committed
128

129
    expect(state(), equals('BC')); // transition ->1 is at 1.0
Hixie's avatar
Hixie committed
130

131
    navigator.pushNamed('/2');
132
    expect(state(), equals('BC')); // transition 1->2 is not yet built
133
    await tester.pump();
134 135
    expect(state(), equals('BC')); // transition 1->2 is at 0.0
    expect(state(skipOffstage: false), equals('BCE')); // E is offstage
Hixie's avatar
Hixie committed
136

137
    await tester.pump(kFourTenthsOfTheTransitionDuration);
138
    expect(state(), equals('BCE')); // transition 1->2 is at 0.4
Hixie's avatar
Hixie committed
139

140
    await tester.pump(kFourTenthsOfTheTransitionDuration);
141
    expect(state(), equals('BDE')); // transition 1->2 is at 0.8
Hixie's avatar
Hixie committed
142

143
    await tester.pump(kFourTenthsOfTheTransitionDuration);
144
    expect(state(), equals('E')); // transition 1->2 is at 1.0
145
    expect(state(skipOffstage: false), equals('E')); // B and C are gone, the route is inactive with maintainState=false
Hixie's avatar
Hixie committed
146

147
    navigator.pop();
148
    expect(state(), equals('E')); // transition 1<-2 is at 1.0, just reversed
149
    await tester.pump();
Hans Muller's avatar
Hans Muller committed
150 151
    await tester.pump();

152
    expect(state(), equals('BDE')); // transition 1<-2 is at 1.0
Hixie's avatar
Hixie committed
153

154
    await tester.pump(kFourTenthsOfTheTransitionDuration);
155
    expect(state(), equals('BDE')); // transition 1<-2 is at 0.6
Hixie's avatar
Hixie committed
156

157
    navigator.pushNamed('/3');
158
    expect(state(), equals('BDE')); // transition 1<-2 is at 0.6
159
    await tester.pump();
160 161
    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
Hixie's avatar
Hixie committed
162

163
    await tester.pump(kFourTenthsOfTheTransitionDuration);
164
    expect(state(), equals('BCEF')); // transition 1<-2 is at 0.2, 1->3 is at 0.4
165
    expect(state(skipOffstage: false), equals('BCEF')); // nothing secret going on here
Hixie's avatar
Hixie committed
166

167
    await tester.pump(kFourTenthsOfTheTransitionDuration);
168
    expect(state(), equals('BDF')); // transition 1<-2 is done, 1->3 is at 0.8
Hixie's avatar
Hixie committed
169

170
    navigator.pop();
171
    expect(state(), equals('BDF')); // transition 1<-3 is at 0.8, just reversed
172
    await tester.pump();
173
    expect(state(), equals('BDF')); // transition 1<-3 is at 0.8
Hixie's avatar
Hixie committed
174

175
    await tester.pump(kTwoTenthsOfTheTransitionDuration); // notice that dT=0.2 here, not 0.4
176
    expect(state(), equals('BDF')); // transition 1<-3 is at 0.6
Hixie's avatar
Hixie committed
177

178
    await tester.pump(kFourTenthsOfTheTransitionDuration);
179
    expect(state(), equals('BCF')); // transition 1<-3 is at 0.2
Hixie's avatar
Hixie committed
180

181
    navigator.pushNamed('/4');
182
    expect(state(), equals('BCF')); // transition 1<-3 is at 0.2, 1->4 is not yet built
183
    await tester.pump();
184 185
    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
Hixie's avatar
Hixie committed
186

187
    await tester.pump(kFourTenthsOfTheTransitionDuration);
188
    expect(state(), equals('BCG')); // transition 1<-3 is done, 1->4 is at 0.4
Hixie's avatar
Hixie committed
189

190
    await tester.pump(kFourTenthsOfTheTransitionDuration);
191
    expect(state(), equals('BDG')); // transition 1->4 is at 0.8
Hixie's avatar
Hixie committed
192

193
    await tester.pump(kFourTenthsOfTheTransitionDuration);
194
    expect(state(), equals('G')); // transition 1->4 is done
195
    expect(state(skipOffstage: false), equals('G')); // route 1 is not around any more
Hixie's avatar
Hixie committed
196 197

  });
198 199 200

  testWidgets('Check onstage/offstage handling of barriers around transitions', (WidgetTester tester) async {
    await tester.pumpWidget(
201
      MaterialApp(
202 203
        onGenerateRoute: (RouteSettings settings) {
          switch (settings.name) {
204 205
            case '/': return TestRoute<void>(settings: settings, child: const Text('A'));
            case '/1': return TestRoute<void>(settings: settings, barrierColor: const Color(0xFFFFFF00), child: const Text('B'));
206
          }
207
          return null;
208 209
        },
      ),
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
    );
    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));

  });
Hixie's avatar
Hixie committed
226
}