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

5 6
// @dart = 2.8

Adam Barth's avatar
Adam Barth committed
7
import 'package:flutter_test/flutter_test.dart';
8 9
import 'package:flutter/material.dart';

10 11
class TestOverlayRoute extends OverlayRoute<void> {
  TestOverlayRoute({ RouteSettings settings }) : super(settings: settings);
12
  @override
13
  Iterable<OverlayEntry> createOverlayEntries() sync* {
14
    yield OverlayEntry(builder: _build);
15
  }
16
  Widget _build(BuildContext context) => const Text('Overlay');
17 18
}

19
class PersistentBottomSheetTest extends StatefulWidget {
20
  const PersistentBottomSheetTest({ Key key }) : super(key: key);
21 22

  @override
23
  PersistentBottomSheetTestState createState() => PersistentBottomSheetTestState();
24 25 26
}

class PersistentBottomSheetTestState extends State<PersistentBottomSheetTest> {
27
  final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
28 29 30 31

  bool setStateCalled = false;

  void showBottomSheet() {
32
    _scaffoldKey.currentState.showBottomSheet<void>((BuildContext context) {
33
      return const Text('bottomSheet');
34
    })
35
    .closed.whenComplete(() {
36 37 38 39 40 41 42 43
      setState(() {
        setStateCalled = true;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
44
    return Scaffold(
45
      key: _scaffoldKey,
46
      body: const Text('Sheet'),
47 48 49 50
    );
  }
}

51
void main() {
52
  testWidgets('Check onstage/offstage handling around transitions', (WidgetTester tester) async {
53 54
    final GlobalKey containerKey1 = GlobalKey();
    final GlobalKey containerKey2 = GlobalKey();
55
    final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
56 57
      '/': (_) => Container(key: containerKey1, child: const Text('Home')),
      '/settings': (_) => Container(key: containerKey2, child: const Text('Settings')),
58
    };
59

60
    await tester.pumpWidget(MaterialApp(routes: routes));
61

62
    expect(find.text('Home'), isOnstage);
63 64
    expect(find.text('Settings'), findsNothing);
    expect(find.text('Overlay'), findsNothing);
65

66 67 68
    expect(Navigator.canPop(containerKey1.currentContext), isFalse);
    Navigator.pushNamed(containerKey1.currentContext, '/settings');
    expect(Navigator.canPop(containerKey1.currentContext), isTrue);
69

70
    await tester.pump();
71

72 73
    expect(find.text('Home'), isOnstage);
    expect(find.text('Settings', skipOffstage: false), isOffstage);
74
    expect(find.text('Overlay'), findsNothing);
75

76
    await tester.pump(const Duration(milliseconds: 16));
77

78 79
    expect(find.text('Home'), isOnstage);
    expect(find.text('Settings'), isOnstage);
80
    expect(find.text('Overlay'), findsNothing);
81

82
    await tester.pump(const Duration(seconds: 1));
83

84
    expect(find.text('Home'), findsNothing);
85
    expect(find.text('Settings'), isOnstage);
86
    expect(find.text('Overlay'), findsNothing);
87

88
    Navigator.push(containerKey2.currentContext, TestOverlayRoute());
89

90
    await tester.pump();
91

92
    expect(find.text('Home'), findsNothing);
93 94
    expect(find.text('Settings'), isOnstage);
    expect(find.text('Overlay'), isOnstage);
95

96
    await tester.pump(const Duration(seconds: 1));
97

98
    expect(find.text('Home'), findsNothing);
99 100
    expect(find.text('Settings'), isOnstage);
    expect(find.text('Overlay'), isOnstage);
101

102 103
    expect(Navigator.canPop(containerKey2.currentContext), isTrue);
    Navigator.pop(containerKey2.currentContext);
104
    await tester.pump();
105

106
    expect(find.text('Home'), findsNothing);
107
    expect(find.text('Settings'), isOnstage);
108
    expect(find.text('Overlay'), findsNothing);
109

110
    await tester.pump(const Duration(seconds: 1));
111

112
    expect(find.text('Home'), findsNothing);
113
    expect(find.text('Settings'), isOnstage);
114
    expect(find.text('Overlay'), findsNothing);
115

116 117
    expect(Navigator.canPop(containerKey2.currentContext), isTrue);
    Navigator.pop(containerKey2.currentContext);
118
    await tester.pump();
Hans Muller's avatar
Hans Muller committed
119
    await tester.pump();
120

121 122
    expect(find.text('Home'), isOnstage);
    expect(find.text('Settings'), isOnstage);
123
    expect(find.text('Overlay'), findsNothing);
124

125
    await tester.pump(const Duration(seconds: 1));
126

127
    expect(find.text('Home'), isOnstage);
128 129
    expect(find.text('Settings'), findsNothing);
    expect(find.text('Overlay'), findsNothing);
130

131
    expect(Navigator.canPop(containerKey1.currentContext), isFalse);
132 133
  });

134
  testWidgets('Check back gesture disables Heroes', (WidgetTester tester) async {
135 136
    final GlobalKey containerKey1 = GlobalKey();
    final GlobalKey containerKey2 = GlobalKey();
137 138
    const String kHeroTag = 'hero';
    final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
139
      '/': (_) => Scaffold(
140
        key: containerKey1,
141
        body: Container(
142
          color: const Color(0xff00ffff),
143
          child: const Hero(
144
            tag: kHeroTag,
145 146 147
            child: Text('Home'),
          ),
        ),
148
      ),
149
      '/settings': (_) => Scaffold(
150
        key: containerKey2,
151
        body: Container(
152
          padding: const EdgeInsets.all(100.0),
153
          color: const Color(0xffff00ff),
154
          child: const Hero(
155
            tag: kHeroTag,
156 157 158
            child: Text('Settings'),
          ),
        ),
159 160 161
      ),
    };

Dan Field's avatar
Dan Field committed
162
    await tester.pumpWidget(MaterialApp(routes: routes));
163 164 165 166 167 168 169 170 171

    Navigator.pushNamed(containerKey1.currentContext, '/settings');

    await tester.pump();
    await tester.pump(const Duration(milliseconds: 16));

    expect(find.text('Settings'), isOnstage);

    // Settings text is heroing to its new location
172 173 174 175 176
    Offset settingsOffset = tester.getTopLeft(find.text('Settings'));
    expect(settingsOffset.dx, greaterThan(0.0));
    expect(settingsOffset.dx, lessThan(100.0));
    expect(settingsOffset.dy, greaterThan(0.0));
    expect(settingsOffset.dy, lessThan(100.0));
177 178 179 180 181 182 183

    await tester.pump(const Duration(seconds: 1));

    expect(find.text('Home'), findsNothing);
    expect(find.text('Settings'), isOnstage);

    // Drag from left edge to invoke the gesture.
184
    final TestGesture gesture = await tester.startGesture(const Offset(5.0, 100.0));
185
    await gesture.moveBy(const Offset(50.0, 0.0));
186 187 188 189 190 191 192
    await tester.pump();

    // Home is now visible.
    expect(find.text('Home'), isOnstage);
    expect(find.text('Settings'), isOnstage);

    // Home page is sliding in from the left, no heroes.
193 194 195
    final Offset homeOffset = tester.getTopLeft(find.text('Home'));
    expect(homeOffset.dx, lessThan(0.0));
    expect(homeOffset.dy, 0.0);
196 197 198

    // Settings page is sliding off to the right, no heroes.
    settingsOffset = tester.getTopLeft(find.text('Settings'));
199 200
    expect(settingsOffset.dx, greaterThan(100.0));
    expect(settingsOffset.dy, 100.0);
Dan Field's avatar
Dan Field committed
201
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS,  TargetPlatform.macOS }));
202

203
  testWidgets("Check back gesture doesn't start during transitions", (WidgetTester tester) async {
204 205
    final GlobalKey containerKey1 = GlobalKey();
    final GlobalKey containerKey2 = GlobalKey();
206
    final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
207 208
      '/': (_) => Scaffold(key: containerKey1, body: const Text('Home')),
      '/settings': (_) => Scaffold(key: containerKey2, body: const Text('Settings')),
209 210
    };

Dan Field's avatar
Dan Field committed
211
    await tester.pumpWidget(MaterialApp(routes: routes));
212 213 214 215 216 217 218 219 220 221 222 223

    Navigator.pushNamed(containerKey1.currentContext, '/settings');

    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    // We are mid-transition, both pages are on stage.
    expect(find.text('Home'), isOnstage);
    expect(find.text('Settings'), isOnstage);

    // Drag from left edge to invoke the gesture. (near bottom so we grab
    // the Settings page as it comes up).
224
    TestGesture gesture = await tester.startGesture(const Offset(5.0, 550.0));
225
    await gesture.moveBy(const Offset(500.0, 0.0));
226 227 228 229 230 231 232 233 234 235
    await gesture.up();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 1000));

    // The original forward navigation should have completed, instead of the
    // back gesture, since we were mid transition.
    expect(find.text('Home'), findsNothing);
    expect(find.text('Settings'), isOnstage);

    // Try again now that we're settled.
236
    gesture = await tester.startGesture(const Offset(5.0, 550.0));
237
    await gesture.moveBy(const Offset(500.0, 0.0));
238 239 240 241 242 243
    await gesture.up();
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 1000));

    expect(find.text('Home'), isOnstage);
    expect(find.text('Settings'), findsNothing);
Dan Field's avatar
Dan Field committed
244
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS,  TargetPlatform.macOS }));
245 246 247

  // Tests bug https://github.com/flutter/flutter/issues/6451
  testWidgets('Check back gesture with a persistent bottom sheet showing', (WidgetTester tester) async {
248 249
    final GlobalKey containerKey1 = GlobalKey();
    final GlobalKey containerKey2 = GlobalKey();
250
    final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
251 252
      '/': (_) => Scaffold(key: containerKey1, body: const Text('Home')),
      '/sheet': (_) => PersistentBottomSheetTest(key: containerKey2),
253 254
    };

Dan Field's avatar
Dan Field committed
255
    await tester.pumpWidget(MaterialApp(routes: routes));
256 257 258 259 260 261 262 263 264

    Navigator.pushNamed(containerKey1.currentContext, '/sheet');

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    expect(find.text('Home'), findsNothing);
    expect(find.text('Sheet'), isOnstage);

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
    // Drag from left edge to invoke the gesture. We should go back.
    TestGesture gesture = await tester.startGesture(const Offset(5.0, 100.0));
    await gesture.moveBy(const Offset(500.0, 0.0));
    await gesture.up();
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    Navigator.pushNamed(containerKey1.currentContext, '/sheet');

    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

    expect(find.text('Home'), findsNothing);
    expect(find.text('Sheet'), isOnstage);

280
    // Show the bottom sheet.
281
    final PersistentBottomSheetTestState sheet = containerKey2.currentState as PersistentBottomSheetTestState;
282 283 284 285
    sheet.showBottomSheet();

    await tester.pump(const Duration(seconds: 1));

286 287
    // Drag from left edge to invoke the gesture. Nothing should happen.
    gesture = await tester.startGesture(const Offset(5.0, 100.0));
288
    await gesture.moveBy(const Offset(500.0, 0.0));
289 290 291 292
    await gesture.up();
    await tester.pump();
    await tester.pump(const Duration(seconds: 1));

293 294
    expect(find.text('Home'), findsNothing);
    expect(find.text('Sheet'), isOnstage);
295

296 297
    // Sheet did not call setState (since the gesture did nothing).
    expect(sheet.setStateCalled, isFalse);
Dan Field's avatar
Dan Field committed
298
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS,  TargetPlatform.macOS }));
299 300 301

  testWidgets('Test completed future', (WidgetTester tester) async {
    final Map<String, WidgetBuilder> routes = <String, WidgetBuilder>{
302 303
      '/': (_) => const Center(child: Text('home')),
      '/next': (_) => const Center(child: Text('next')),
304 305
    };

306
    await tester.pumpWidget(MaterialApp(routes: routes));
307

308
    final PageRoute<void> route = MaterialPageRoute<void>(
309
      settings: const RouteSettings(name: '/page'),
310
      builder: (BuildContext context) => const Center(child: Text('page')),
311 312 313
    );

    int popCount = 0;
314 315
    route.popped.whenComplete(() {
      popCount += 1;
316 317 318
    });

    int completeCount = 0;
319 320
    route.completed.whenComplete(() {
      completeCount += 1;
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375
    });

    expect(popCount, 0);
    expect(completeCount, 0);

    Navigator.push(tester.element(find.text('home')), route);

    expect(popCount, 0);
    expect(completeCount, 0);

    await tester.pump();

    expect(popCount, 0);
    expect(completeCount, 0);

    await tester.pump(const Duration(milliseconds: 100));

    expect(popCount, 0);
    expect(completeCount, 0);

    await tester.pump(const Duration(milliseconds: 100));

    expect(popCount, 0);
    expect(completeCount, 0);

    await tester.pump(const Duration(seconds: 1));

    expect(popCount, 0);
    expect(completeCount, 0);

    Navigator.pop(tester.element(find.text('page')));

    expect(popCount, 0);
    expect(completeCount, 0);

    await tester.pump();

    expect(popCount, 1);
    expect(completeCount, 0);

    await tester.pump(const Duration(milliseconds: 100));

    expect(popCount, 1);
    expect(completeCount, 0);

    await tester.pump(const Duration(milliseconds: 100));

    expect(popCount, 1);
    expect(completeCount, 0);

    await tester.pump(const Duration(seconds: 1));

    expect(popCount, 1);
    expect(completeCount, 1);
  });
376
}