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.

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

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

17
class PersistentBottomSheetTest extends StatefulWidget {
18
  const PersistentBottomSheetTest({ Key? key }) : super(key: key);
19 20

  @override
21
  PersistentBottomSheetTestState createState() => PersistentBottomSheetTestState();
22 23 24
}

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

  bool setStateCalled = false;

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

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

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

58
    await tester.pumpWidget(MaterialApp(routes: routes));
59

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

64 65 66
    expect(Navigator.canPop(containerKey1.currentContext!), isFalse);
    Navigator.pushNamed(containerKey1.currentContext!, '/settings');
    expect(Navigator.canPop(containerKey1.currentContext!), isTrue);
67

68
    await tester.pump();
69

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

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

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

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

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

86
    Navigator.push(containerKey2.currentContext!, TestOverlayRoute());
87

88
    await tester.pump();
89

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

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

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

100 101
    expect(Navigator.canPop(containerKey2.currentContext!), isTrue);
    Navigator.pop(containerKey2.currentContext!);
102
    await tester.pump();
103

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

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

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

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

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

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

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

129
    expect(Navigator.canPop(containerKey1.currentContext!), isFalse);
130 131
  });

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

Dan Field's avatar
Dan Field committed
160
    await tester.pumpWidget(MaterialApp(routes: routes));
161

162
    Navigator.pushNamed(containerKey1.currentContext!, '/settings');
163 164 165 166 167 168 169

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

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

    // Settings text is heroing to its new location
170 171 172 173 174
    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));
175 176 177 178 179 180 181

    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.
182
    final TestGesture gesture = await tester.startGesture(const Offset(5.0, 100.0));
183
    await gesture.moveBy(const Offset(50.0, 0.0));
184 185 186 187 188 189 190
    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.
191 192 193
    final Offset homeOffset = tester.getTopLeft(find.text('Home'));
    expect(homeOffset.dx, lessThan(0.0));
    expect(homeOffset.dy, 0.0);
194 195 196

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

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

Dan Field's avatar
Dan Field committed
209
    await tester.pumpWidget(MaterialApp(routes: routes));
210

211
    Navigator.pushNamed(containerKey1.currentContext!, '/settings');
212 213 214 215 216 217 218 219 220 221

    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).
222
    TestGesture gesture = await tester.startGesture(const Offset(5.0, 550.0));
223
    await gesture.moveBy(const Offset(500.0, 0.0));
224 225 226 227 228 229 230 231 232 233
    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.
234
    gesture = await tester.startGesture(const Offset(5.0, 550.0));
235
    await gesture.moveBy(const Offset(500.0, 0.0));
236 237 238 239 240 241
    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
242
  }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS,  TargetPlatform.macOS }));
243 244 245

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

Dan Field's avatar
Dan Field committed
253
    await tester.pumpWidget(MaterialApp(routes: routes));
254

255
    Navigator.pushNamed(containerKey1.currentContext!, '/sheet');
256 257 258 259 260 261 262

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

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

263 264 265 266 267 268 269
    // 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));

270
    Navigator.pushNamed(containerKey1.currentContext!, '/sheet');
271 272 273 274 275 276 277

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

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

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

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

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

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

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

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

304
    await tester.pumpWidget(MaterialApp(routes: routes));
305

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

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

    int completeCount = 0;
317 318
    route.completed.whenComplete(() {
      completeCount += 1;
319 320 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
    });

    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);
  });
374
}